aboutsummaryrefslogtreecommitdiffstats
path: root/cps-service
diff options
context:
space:
mode:
Diffstat (limited to 'cps-service')
-rw-r--r--cps-service/src/main/java/org/onap/cps/api/CpsFacade.java96
-rw-r--r--cps-service/src/main/java/org/onap/cps/api/CpsQueryService.java13
-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/api/parameters/FetchDescendantsOption.java32
-rw-r--r--cps-service/src/main/java/org/onap/cps/impl/CpsDataServiceImpl.java252
-rw-r--r--cps-service/src/main/java/org/onap/cps/impl/CpsFacadeImpl.java97
-rw-r--r--cps-service/src/main/java/org/onap/cps/impl/CpsNotificationServiceImpl.java31
-rw-r--r--cps-service/src/main/java/org/onap/cps/impl/CpsQueryServiceImpl.java9
-rw-r--r--cps-service/src/main/java/org/onap/cps/impl/DataNodeFactoryImpl.java107
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java4
-rw-r--r--cps-service/src/main/java/org/onap/cps/utils/DataMapper.java133
-rw-r--r--cps-service/src/main/java/org/onap/cps/utils/PrefixResolver.java26
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/api/parameters/FetchDescendantsOptionSpec.groovy27
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/impl/CpsAnchorServiceImplSpec.groovy1
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/impl/CpsDataServiceImplSpec.groovy44
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/impl/CpsFacadeImplSpec.groovy114
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/impl/CpsNotificationServiceImplSpec.groovy11
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/impl/CpsQueryServiceImplSpec.groovy8
-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.groovy59
20 files changed, 1042 insertions, 301 deletions
diff --git a/cps-service/src/main/java/org/onap/cps/api/CpsFacade.java b/cps-service/src/main/java/org/onap/cps/api/CpsFacade.java
new file mode 100644
index 0000000000..8933f02cb4
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/api/CpsFacade.java
@@ -0,0 +1,96 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 Nordix Foundation
+ * ================================================================================
+ * 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.List;
+import java.util.Map;
+import org.onap.cps.api.parameters.FetchDescendantsOption;
+import org.onap.cps.api.parameters.PaginationOption;
+
+public interface CpsFacade {
+
+ /**
+ * Get the first data node for a given dataspace, anchor and xpath.
+ *
+ * @param dataspaceName the name of the dataspace
+ * @param anchorName the name of the anchor
+ * @param xpath the xpath
+ * @param fetchDescendantsOption control what level of descendants should be returned
+ * @return a map representing the data node and its descendants
+ */
+ Map<String, Object> getFirstDataNodeByAnchor(String dataspaceName,
+ String anchorName,
+ String xpath,
+ FetchDescendantsOption fetchDescendantsOption);
+
+ /**
+ * Get data nodes for a given dataspace, anchor and xpath.
+ *
+ * @param dataspaceName the name of the dataspace
+ * @param anchorName the name of the anchor
+ * @param xpath the xpath
+ * @param fetchDescendantsOption control what level of descendants should be returned
+ * @return a map representing the data nodes and their descendants
+ */
+ List<Map<String, Object>> getDataNodesByAnchor(String dataspaceName,
+ String anchorName,
+ String xpath,
+ FetchDescendantsOption fetchDescendantsOption);
+
+ /**
+ * Query the given anchor using a cps path expression.
+ *
+ * @param dataspaceName the name of the dataspace
+ * @param anchorName the name of the anchor
+ * @param cpsPath the xpath i.e. query
+ * @param fetchDescendantsOption control what level of descendants should be returned
+ * @return a map representing the data nodes and their descendants
+ */
+ List<Map<String, Object>> executeAnchorQuery(String dataspaceName,
+ String anchorName,
+ String cpsPath,
+ FetchDescendantsOption fetchDescendantsOption);
+
+ /**
+ * Query the given dataspace (all anchors) using a cps path expression.
+ *
+ * @param dataspaceName the name of the dataspace
+ * @param cpsPath the xpath i.e. query
+ * @param fetchDescendantsOption control what level of descendants should be returned
+ * @return a map representing the data nodes and their descendants
+ */
+ List<Map<String, Object>> executeDataspaceQuery(String dataspaceName,
+ String cpsPath,
+ FetchDescendantsOption fetchDescendantsOption,
+ PaginationOption paginationOption);
+
+ /**
+ * Query how many anchors wil be returned for the given dataspace and a cps path query.
+ *
+ * @param dataspaceName the name of the dataspace
+ * @param cpsPath the xpath i.e. query
+ * @param paginationOption the options for pagination
+ * @return the number of anchors involved in the output
+ */
+ int countAnchorsInDataspaceQuery(String dataspaceName,
+ String cpsPath,
+ PaginationOption paginationOption);
+}
diff --git a/cps-service/src/main/java/org/onap/cps/api/CpsQueryService.java b/cps-service/src/main/java/org/onap/cps/api/CpsQueryService.java
index 30c8bbbdf3..d6c1f7fc60 100644
--- a/cps-service/src/main/java/org/onap/cps/api/CpsQueryService.java
+++ b/cps-service/src/main/java/org/onap/cps/api/CpsQueryService.java
@@ -74,6 +74,19 @@ public interface CpsQueryService {
<T> Set<T> queryDataLeaf(String dataspaceName, String anchorName, String cpsPath, Class<T> targetClass);
/**
+ * Get data leaf for the given dataspace and anchor by cps path.
+ *
+ * @param dataspaceName dataspace name
+ * @param anchorName anchor name
+ * @param cpsPath cps path
+ * @param queryResultLimit the maximum number of data nodes to return; if less than 1, returns all matching nodes
+ * @param targetClass class of the expected data type
+ * @return a collection of data objects of expected type
+ */
+ <T> Set<T> queryDataLeaf(String dataspaceName, String anchorName, String cpsPath, int queryResultLimit,
+ Class<T> targetClass);
+
+ /**
* Get data nodes for the given dataspace across all anchors by cps path.
*
* @param dataspaceName dataspace name
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/api/parameters/FetchDescendantsOption.java b/cps-service/src/main/java/org/onap/cps/api/parameters/FetchDescendantsOption.java
index 46022ba46b..05fa366239 100644
--- a/cps-service/src/main/java/org/onap/cps/api/parameters/FetchDescendantsOption.java
+++ b/cps-service/src/main/java/org/onap/cps/api/parameters/FetchDescendantsOption.java
@@ -1,7 +1,7 @@
/*
* ============LICENSE_START=======================================================
* Copyright (C) 2021 Pantheon.tech
- * Copyright (C) 2022-2023 Nordix Foundation
+ * Copyright (C) 2022-2025 Nordix Foundation
* Modifications Copyright (C) 2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -24,6 +24,7 @@ package org.onap.cps.api.parameters;
import com.google.common.base.Strings;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.onap.cps.api.exceptions.DataValidationException;
@@ -44,6 +45,12 @@ public class FetchDescendantsOption {
private static final Pattern FETCH_DESCENDANTS_OPTION_PATTERN =
Pattern.compile("^$|^all$|^none$|^direct$|^[0-9]+$|^-1$|^1$");
+ /**
+ * Get depth.
+ *
+ * @return depth: -1 for all descendants, 0 for no descendants, or positive value for fixed level of descendants
+ */
+ @Getter
private final int depth;
private final String optionName;
@@ -76,15 +83,7 @@ public class FetchDescendantsOption {
}
/**
- * Get depth.
- * @return depth: -1 for all descendants, 0 for no descendants, or positive value for fixed level of descendants
- */
- public int getDepth() {
- return depth;
- }
-
- /**
- * get fetch descendants option for given descendant.
+ * Convert fetch descendants option from string to enum with depth.
*
* @param fetchDescendantsOptionAsString fetch descendants option string
* @return fetch descendants option for given descendant
@@ -99,11 +98,22 @@ public class FetchDescendantsOption {
} else if ("1".equals(fetchDescendantsOptionAsString) || "direct".equals(fetchDescendantsOptionAsString)) {
return FetchDescendantsOption.DIRECT_CHILDREN_ONLY;
} else {
- final Integer depth = Integer.valueOf(fetchDescendantsOptionAsString);
+ final int depth = Integer.parseInt(fetchDescendantsOptionAsString);
return new FetchDescendantsOption(depth);
}
}
+ /**
+ * Convert include all-descendants boolean parameter to FetchDescendantsOption enum.
+ *
+ * @param includedDescendantsOptionAsBoolean fetch descendants option as Boolean
+ * @return fetch descendants option for given descendant
+ */
+ public static FetchDescendantsOption getFetchDescendantsOption(final Boolean includedDescendantsOptionAsBoolean) {
+ return Boolean.TRUE.equals(includedDescendantsOptionAsBoolean)
+ ? FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS : FetchDescendantsOption.OMIT_DESCENDANTS;
+ }
+
@Override
public String toString() {
return optionName;
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..a93bf9ac82 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
@@ -1,9 +1,9 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2021-2024 Nordix Foundation
+ * Copyright (C) 2021-2025 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,13 +24,16 @@
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 static org.onap.cps.utils.ContentType.JSON;
+
import io.micrometer.core.annotation.Timed;
import java.io.Serializable;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -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;
@@ -50,11 +53,9 @@ import org.onap.cps.events.model.Data.Operation;
import org.onap.cps.spi.CpsDataPersistenceService;
import org.onap.cps.utils.ContentType;
import org.onap.cps.utils.CpsValidator;
-import org.onap.cps.utils.DataMapUtils;
+import org.onap.cps.utils.DataMapper;
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,36 +63,33 @@ 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;
private final CpsDeltaService cpsDeltaService;
+ private final DataMapper dataMapper;
private final JsonObjectMapper jsonObjectMapper;
- private final PrefixResolver prefixResolver;
@Override
public void saveData(final String dataspaceName, final String anchorName, final String nodeData,
final OffsetDateTime observedTimestamp) {
- saveData(dataspaceName, anchorName, nodeData, observedTimestamp, ContentType.JSON);
+ saveData(dataspaceName, anchorName, nodeData, observedTimestamp, JSON);
}
@Override
- @Timed(value = "cps.data.service.datanode.root.save",
- description = "Time taken to save a root data node")
+ @Timed(value = "cps.data.service.datanode.root.save", description = "Time taken to save a root data node")
public void saveData(final String dataspaceName, final String anchorName, final String nodeData,
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);
}
@@ -99,34 +97,32 @@ public class CpsDataServiceImpl implements CpsDataService {
@Override
public void saveData(final String dataspaceName, final String anchorName, final String parentNodeXpath,
final String nodeData, final OffsetDateTime observedTimestamp) {
- saveData(dataspaceName, anchorName, parentNodeXpath, nodeData, observedTimestamp, ContentType.JSON);
+ saveData(dataspaceName, anchorName, parentNodeXpath, nodeData, observedTimestamp, JSON);
}
@Override
- @Timed(value = "cps.data.service.datanode.child.save",
- description = "Time taken to save a child data node")
+ @Timed(value = "cps.data.service.datanode.child.save", description = "Time taken to save a child data node")
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 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);
}
@Override
- @Timed(value = "cps.data.service.list.element.save",
- description = "Time taken to save list elements")
+ @Timed(value = "cps.data.service.list.element.save", description = "Time taken to save list elements")
public void saveListElements(final String dataspaceName, final String anchorName,
final String parentNodeXpath, final String nodeData,
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,
@@ -136,8 +132,7 @@ public class CpsDataServiceImpl implements CpsDataService {
}
@Override
- @Timed(value = "cps.data.service.datanode.get",
- description = "Time taken to get data nodes for an xpath")
+ @Timed(value = "cps.data.service.datanode.get", description = "Time taken to get data nodes for an xpath")
public Collection<DataNode> getDataNodes(final String dataspaceName, final String anchorName,
final String xpath,
final FetchDescendantsOption fetchDescendantsOption) {
@@ -146,8 +141,7 @@ public class CpsDataServiceImpl implements CpsDataService {
}
@Override
- @Timed(value = "cps.data.service.datanode.batch.get",
- description = "Time taken to get a batch of data nodes")
+ @Timed(value = "cps.data.service.datanode.batch.get", description = "Time taken to get a batch of data nodes")
public Collection<DataNode> getDataNodesForMultipleXpaths(final String dataspaceName, final String anchorName,
final Collection<String> xpaths,
final FetchDescendantsOption fetchDescendantsOption) {
@@ -163,8 +157,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 +174,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,
+ JSON);
for (final DataNode dataNodeUpdate : dataNodeUpdates) {
processDataNodeUpdate(anchor, dataNodeUpdate);
}
@@ -210,8 +205,7 @@ public class CpsDataServiceImpl implements CpsDataService {
}
@Override
- @Timed(value = "cps.data.service.get.delta",
- description = "Time taken to get delta between anchors")
+ @Timed(value = "cps.data.service.get.delta", description = "Time taken to get delta between anchors")
public List<DeltaReport> getDeltaByDataspaceAndAnchors(final String dataspaceName,
final String sourceAnchorName,
final String targetAnchorName, final String xpath,
@@ -225,9 +219,9 @@ public class CpsDataServiceImpl implements CpsDataService {
return cpsDeltaService.getDeltaReports(sourceDataNodes, targetDataNodes);
}
+ @Override
@Timed(value = "cps.data.service.get.deltaBetweenAnchorAndPayload",
description = "Time taken to get delta between anchor and a payload")
- @Override
public List<DeltaReport> getDeltaByDataspaceAnchorAndPayload(final String dataspaceName,
final String sourceAnchorName, final String xpath,
final Map<String, String> yangResourceContentPerName,
@@ -256,8 +250,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,31 +260,30 @@ 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));
}
@Override
- @Timed(value = "cps.data.service.list.update",
- description = "Time taken to update a list")
+ @Timed(value = "cps.data.service.list.update", description = "Time taken to update a list")
public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
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);
}
@Override
- @Timed(value = "cps.data.service.list.batch.update",
- description = "Time taken to update a batch of lists")
+ @Timed(value = "cps.data.service.list.batch.update", description = "Time taken to update a batch of lists")
public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
final Collection<DataNode> dataNodes, final OffsetDateTime observedTimestamp) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
@@ -300,8 +293,7 @@ public class CpsDataServiceImpl implements CpsDataService {
}
@Override
- @Timed(value = "cps.data.service.datanode.delete",
- description = "Time taken to delete a datanode")
+ @Timed(value = "cps.data.service.datanode.delete", description = "Time taken to delete a datanode")
public void deleteDataNode(final String dataspaceName, final String anchorName, final String dataNodeXpath,
final OffsetDateTime observedTimestamp) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
@@ -311,8 +303,7 @@ public class CpsDataServiceImpl implements CpsDataService {
}
@Override
- @Timed(value = "cps.data.service.datanode.batch.delete",
- description = "Time taken to delete a batch of datanodes")
+ @Timed(value = "cps.data.service.datanode.batch.delete", description = "Time taken to delete a batch of datanodes")
public void deleteDataNodes(final String dataspaceName, final String anchorName,
final Collection<String> dataNodeXpaths, final OffsetDateTime observedTimestamp) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
@@ -348,8 +339,7 @@ public class CpsDataServiceImpl implements CpsDataService {
}
@Override
- @Timed(value = "cps.data.service.list.delete",
- description = "Time taken to delete a list or list element")
+ @Timed(value = "cps.data.service.list.delete", description = "Time taken to delete a list or list element")
public void deleteListOrListElement(final String dataspaceName, final String anchorName, final String listNodeXpath,
final OffsetDateTime observedTimestamp) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
@@ -362,156 +352,34 @@ 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);
}
- private Collection<DataNode> rebuildSourceDataNodes(final String xpath, final Anchor sourceAnchor,
+ private Collection<DataNode> rebuildSourceDataNodes(final String xpath,
+ final Anchor sourceAnchor,
final Collection<DataNode> sourceDataNodes) {
-
final Collection<DataNode> sourceDataNodesRebuilt = new ArrayList<>();
if (sourceDataNodes != null) {
- final String sourceDataNodesAsJson = getDataNodesAsJson(sourceAnchor, sourceDataNodes);
- sourceDataNodesRebuilt.addAll(
- buildDataNodesWithAnchorAndXpath(sourceAnchor, xpath, sourceDataNodesAsJson, ContentType.JSON));
+ final Map<String, Object> sourceDataNodesAsMap = dataMapper.toFlatDataMap(sourceAnchor, sourceDataNodes);
+ final String sourceDataNodesAsJson = jsonObjectMapper.asJsonString(sourceDataNodesAsMap);
+ final Collection<DataNode> dataNodes = dataNodeFactory
+ .createDataNodesWithAnchorXpathAndNodeData(sourceAnchor, xpath, sourceDataNodesAsJson, JSON);
+ sourceDataNodesRebuilt.addAll(dataNodes);
}
return sourceDataNodesRebuilt;
}
- private Collection<DataNode> buildTargetDataNodes(final Anchor sourceAnchor, final String xpath,
+ private Collection<DataNode> buildTargetDataNodes(final Anchor sourceAnchor,
+ final String xpath,
final Map<String, String> yangResourceContentPerName,
final String targetData) {
if (yangResourceContentPerName.isEmpty()) {
- return buildDataNodesWithAnchorAndXpath(sourceAnchor, xpath, targetData, ContentType.JSON);
- } else {
- return buildDataNodesWithYangResourceAndXpath(yangResourceContentPerName, xpath,
- targetData, ContentType.JSON);
+ return dataNodeFactory.createDataNodesWithAnchorXpathAndNodeData(sourceAnchor, xpath, targetData, JSON);
}
- }
-
- private String getDataNodesAsJson(final Anchor anchor, final Collection<DataNode> dataNodes) {
-
- final List<Map<String, Object>> prefixToDataNodes = prefixResolver(anchor, dataNodes);
- final Map<String, Object> targetDataAsJsonObject = getNodeDataAsJsonString(prefixToDataNodes);
- return jsonObjectMapper.asJsonString(targetDataAsJsonObject);
- }
-
- private Map<String, Object> getNodeDataAsJsonString(final List<Map<String, Object>> prefixToDataNodes) {
- final Map<String, Object> nodeDataAsJson = new HashMap<>();
- for (final Map<String, Object> prefixToDataNode : prefixToDataNodes) {
- nodeDataAsJson.putAll(prefixToDataNode);
- }
- return nodeDataAsJson;
- }
-
- private List<Map<String, Object>> prefixResolver(final Anchor anchor, final Collection<DataNode> dataNodes) {
- final List<Map<String, Object>> prefixToDataNodes = new ArrayList<>(dataNodes.size());
- for (final DataNode dataNode: dataNodes) {
- final String prefix = prefixResolver.getPrefix(anchor, dataNode.getXpath());
- final Map<String, Object> prefixToDataNode = DataMapUtils.toDataMapWithIdentifier(dataNode, prefix);
- prefixToDataNodes.add(prefixToDataNode);
- }
- 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);
+ return dataNodeFactory
+ .createDataNodesWithYangResourceXpathAndNodeData(yangResourceContentPerName, xpath, targetData, JSON);
}
private void processDataNodeUpdate(final Anchor anchor, final DataNode dataNodeUpdate) {
@@ -523,8 +391,10 @@ public class CpsDataServiceImpl implements CpsDataService {
}
}
- private void sendDataUpdatedEvent(final Anchor anchor, final String xpath,
- final Operation operation, final OffsetDateTime observedTimestamp) {
+ private void sendDataUpdatedEvent(final Anchor anchor,
+ final String xpath,
+ final Operation operation,
+ final OffsetDateTime observedTimestamp) {
try {
cpsDataUpdateEventsService.publishCpsDataUpdateEvent(anchor, xpath, operation, observedTimestamp);
} catch (final Exception exception) {
diff --git a/cps-service/src/main/java/org/onap/cps/impl/CpsFacadeImpl.java b/cps-service/src/main/java/org/onap/cps/impl/CpsFacadeImpl.java
new file mode 100644
index 0000000000..4ac0d5d8e8
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/impl/CpsFacadeImpl.java
@@ -0,0 +1,97 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 Nordix Foundation
+ * ================================================================================
+ * 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 java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import org.onap.cps.api.CpsDataService;
+import org.onap.cps.api.CpsFacade;
+import org.onap.cps.api.CpsQueryService;
+import org.onap.cps.api.model.DataNode;
+import org.onap.cps.api.parameters.FetchDescendantsOption;
+import org.onap.cps.api.parameters.PaginationOption;
+import org.onap.cps.utils.DataMapper;
+import org.springframework.stereotype.Service;
+
+@RequiredArgsConstructor
+@Service
+public class CpsFacadeImpl implements CpsFacade {
+
+ private final CpsDataService cpsDataService;
+ private final CpsQueryService cpsQueryService;
+ private final DataMapper dataMapper;
+
+ @Override
+ public Map<String, Object> getFirstDataNodeByAnchor(final String dataspaceName,
+ final String anchorName,
+ final String xpath,
+ final FetchDescendantsOption fetchDescendantsOption) {
+ final DataNode dataNode = cpsDataService.getDataNodes(dataspaceName, anchorName, xpath,
+ fetchDescendantsOption).iterator().next();
+ return dataMapper.toDataMap(dataspaceName, anchorName, dataNode);
+ }
+
+ @Override
+ public List<Map<String, Object>> getDataNodesByAnchor(final String dataspaceName,
+ final String anchorName,
+ final String xpath,
+ final FetchDescendantsOption fetchDescendantsOption) {
+ final Collection<DataNode> dataNodes = cpsDataService.getDataNodes(dataspaceName, anchorName, xpath,
+ fetchDescendantsOption);
+ return dataMapper.toDataMaps(dataspaceName, anchorName, dataNodes);
+ }
+
+ @Override
+ public List<Map<String, Object>> executeAnchorQuery(final String dataspaceName,
+ final String anchorName,
+ final String cpsPath,
+ final FetchDescendantsOption fetchDescendantsOption) {
+ final Collection<DataNode> dataNodes =
+ cpsQueryService.queryDataNodes(dataspaceName, anchorName, cpsPath, fetchDescendantsOption);
+ return dataMapper.toDataMaps(dataspaceName, anchorName, dataNodes);
+ }
+
+ @Override
+ public List<Map<String, Object>> executeDataspaceQuery(final String dataspaceName,
+ final String cpsPath,
+ final FetchDescendantsOption fetchDescendantsOption,
+ final PaginationOption paginationOption) {
+ final Collection<DataNode> dataNodes = cpsQueryService.queryDataNodesAcrossAnchors(dataspaceName,
+ cpsPath, fetchDescendantsOption, paginationOption);
+ return dataMapper.toDataMaps(dataspaceName, dataNodes);
+ }
+
+ @Override
+ public int countAnchorsInDataspaceQuery(final String dataspaceName,
+ final String cpsPath,
+ final PaginationOption paginationOption) {
+ if (paginationOption == PaginationOption.NO_PAGINATION) {
+ return 1;
+ }
+ final int totalAnchors = cpsQueryService.countAnchorsForDataspaceAndCpsPath(dataspaceName, cpsPath);
+ return totalAnchors <= paginationOption.getPageSize() ? 1
+ : (int) Math.ceil((double) totalAnchors / paginationOption.getPageSize());
+ }
+
+}
+
diff --git a/cps-service/src/main/java/org/onap/cps/impl/CpsNotificationServiceImpl.java b/cps-service/src/main/java/org/onap/cps/impl/CpsNotificationServiceImpl.java
index 09ef637965..dc293b26e2 100644
--- a/cps-service/src/main/java/org/onap/cps/impl/CpsNotificationServiceImpl.java
+++ b/cps-service/src/main/java/org/onap/cps/impl/CpsNotificationServiceImpl.java
@@ -1,6 +1,7 @@
/*
* ============LICENSE_START=======================================================
* Copyright (C) 2025 TechMahindra Ltd.
+ * Modifications Copyright (C) 2025 Nordix Foundation
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +23,6 @@ package org.onap.cps.impl;
import static org.onap.cps.api.parameters.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -36,8 +36,7 @@ import org.onap.cps.api.model.DataNode;
import org.onap.cps.cpspath.parser.CpsPathUtil;
import org.onap.cps.spi.CpsDataPersistenceService;
import org.onap.cps.utils.ContentType;
-import org.onap.cps.utils.DataMapUtils;
-import org.onap.cps.utils.PrefixResolver;
+import org.onap.cps.utils.DataMapper;
import org.onap.cps.utils.YangParser;
import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
import org.springframework.stereotype.Service;
@@ -53,10 +52,10 @@ public class CpsNotificationServiceImpl implements CpsNotificationService {
private final YangParser yangParser;
- private final PrefixResolver prefixResolver;
+ private final DataMapper dataMapper;
private static final String ADMIN_DATASPACE = "CPS-Admin";
- private static final String ANCHOR_NAME = "cps-notification-subscriptions";
+ private static final String CPS_SUBSCRIPTION_ANCHOR_NAME = "cps-notification-subscriptions";
private static final String DATASPACE_SUBSCRIPTION_XPATH_FORMAT = "/dataspaces/dataspace[@name='%s']";
private static final String ANCHORS_SUBSCRIPTION_XPATH_FORMAT = "/dataspaces/dataspace[@name='%s']/anchors";
private static final String ANCHOR_SUBSCRIPTION_XPATH_FORMAT =
@@ -65,29 +64,22 @@ public class CpsNotificationServiceImpl implements CpsNotificationService {
@Override
public void createNotificationSubscription(final String notificationSubscriptionAsJson, final String xpath) {
- final Anchor anchor = cpsAnchorService.getAnchor(ADMIN_DATASPACE, ANCHOR_NAME);
+ final Anchor anchor = cpsAnchorService.getAnchor(ADMIN_DATASPACE, CPS_SUBSCRIPTION_ANCHOR_NAME);
final Collection<DataNode> dataNodes =
buildDataNodesWithParentNodeXpath(anchor, xpath, notificationSubscriptionAsJson);
- cpsDataPersistenceService.addListElements(ADMIN_DATASPACE, ANCHOR_NAME, xpath, dataNodes);
+ cpsDataPersistenceService.addListElements(ADMIN_DATASPACE, CPS_SUBSCRIPTION_ANCHOR_NAME, xpath, dataNodes);
}
@Override
public void deleteNotificationSubscription(final String xpath) {
- cpsDataPersistenceService.deleteDataNode(ADMIN_DATASPACE, ANCHOR_NAME, xpath);
+ cpsDataPersistenceService.deleteDataNode(ADMIN_DATASPACE, CPS_SUBSCRIPTION_ANCHOR_NAME, xpath);
}
@Override
public List<Map<String, Object>> getNotificationSubscription(final String xpath) {
- final Collection<DataNode> dataNodes =
- cpsDataPersistenceService.getDataNodes(ADMIN_DATASPACE, ANCHOR_NAME, xpath, INCLUDE_ALL_DESCENDANTS);
- final List<Map<String, Object>> dataMaps = new ArrayList<>(dataNodes.size());
- final Anchor anchor = cpsAnchorService.getAnchor(ADMIN_DATASPACE, ANCHOR_NAME);
- for (final DataNode dataNode: dataNodes) {
- final String prefix = prefixResolver.getPrefix(anchor, dataNode.getXpath());
- final Map<String, Object> dataMap = DataMapUtils.toDataMapWithIdentifier(dataNode, prefix);
- dataMaps.add(dataMap);
- }
- return dataMaps;
+ final Collection<DataNode> dataNodes = cpsDataPersistenceService
+ .getDataNodes(ADMIN_DATASPACE, CPS_SUBSCRIPTION_ANCHOR_NAME, xpath, INCLUDE_ALL_DESCENDANTS);
+ return dataMapper.toDataMaps(ADMIN_DATASPACE, CPS_SUBSCRIPTION_ANCHOR_NAME, dataNodes);
}
@Override
@@ -103,7 +95,8 @@ public class CpsNotificationServiceImpl implements CpsNotificationService {
private boolean isNotificationEnabledForXpath(final String xpath) {
try {
- cpsDataPersistenceService.getDataNodes(ADMIN_DATASPACE, ANCHOR_NAME, xpath, INCLUDE_ALL_DESCENDANTS);
+ cpsDataPersistenceService
+ .getDataNodes(ADMIN_DATASPACE, CPS_SUBSCRIPTION_ANCHOR_NAME, xpath, INCLUDE_ALL_DESCENDANTS);
} catch (final DataNodeNotFoundException e) {
return false;
}
diff --git a/cps-service/src/main/java/org/onap/cps/impl/CpsQueryServiceImpl.java b/cps-service/src/main/java/org/onap/cps/impl/CpsQueryServiceImpl.java
index a3884820c7..5abdd0fe2b 100644
--- a/cps-service/src/main/java/org/onap/cps/impl/CpsQueryServiceImpl.java
+++ b/cps-service/src/main/java/org/onap/cps/impl/CpsQueryServiceImpl.java
@@ -67,8 +67,15 @@ public class CpsQueryServiceImpl implements CpsQueryService {
@Override
public <T> Set<T> queryDataLeaf(final String dataspaceName, final String anchorName, final String cpsPath,
final Class<T> targetClass) {
+ return queryDataLeaf(dataspaceName, anchorName, cpsPath, NO_LIMIT, targetClass);
+ }
+
+ @Override
+ public <T> Set<T> queryDataLeaf(final String dataspaceName, final String anchorName, final String cpsPath,
+ final int queryResultLimit, final Class<T> targetClass) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
- return cpsDataPersistenceService.queryDataLeaf(dataspaceName, anchorName, cpsPath, targetClass);
+ return cpsDataPersistenceService.queryDataLeaf(dataspaceName, anchorName, cpsPath, queryResultLimit,
+ targetClass);
}
@Override
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/main/java/org/onap/cps/spi/CpsDataPersistenceService.java b/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java
index 5be5fb0481..b319929e47 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
@@ -208,10 +208,12 @@ public interface CpsDataPersistenceService {
* @param dataspaceName dataspace name
* @param anchorName anchor name
* @param cpsPath cps path
+ * @param queryResultLimit limits the number of returned entities (if less than 1 returns all)
* @param targetClass class of the expected data type
* @return a collection of data objects of expected type
*/
- <T> Set<T> queryDataLeaf(String dataspaceName, String anchorName, String cpsPath, Class<T> targetClass);
+ <T> Set<T> queryDataLeaf(String dataspaceName, String anchorName, String cpsPath, int queryResultLimit,
+ Class<T> targetClass);
/**
* Get a datanode by dataspace name and cps path across all anchors.
diff --git a/cps-service/src/main/java/org/onap/cps/utils/DataMapper.java b/cps-service/src/main/java/org/onap/cps/utils/DataMapper.java
new file mode 100644
index 0000000000..6e7eff9132
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/utils/DataMapper.java
@@ -0,0 +1,133 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 Nordix Foundation.
+ * ================================================================================
+ * 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.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import lombok.RequiredArgsConstructor;
+import org.onap.cps.api.CpsAnchorService;
+import org.onap.cps.api.model.Anchor;
+import org.onap.cps.api.model.DataNode;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class DataMapper {
+
+ private final CpsAnchorService cpsAnchorService;
+ private final PrefixResolver prefixResolver;
+
+ /**
+ * Convert a data node to a data map.
+ *
+ * @param dataspaceName the name of the dataspace
+ * @param anchorName the name of the anchor
+ * @param dataNode the data node to convert
+ * @return the data node represented as a map of key value pairs
+ */
+ public Map<String, Object> toDataMap(final String dataspaceName, final String anchorName, final DataNode dataNode) {
+ final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
+ final String prefix = prefixResolver.getPrefix(anchor, dataNode.getXpath());
+ return DataMapUtils.toDataMapWithIdentifier(dataNode, prefix);
+ }
+
+ /**
+ * Convert a collection of data nodes to a list of data maps.
+ *
+ * @param dataspaceName the name dataspace name
+ * @param anchorName the name of the anchor
+ * @param dataNodes the data nodes to convert
+ * @return a list of maps representing the data nodes
+ */
+ public List<Map<String, Object>> toDataMaps(final String dataspaceName, final String anchorName,
+ final Collection<DataNode> dataNodes) {
+ final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
+ return toDataMaps(anchor, dataNodes);
+ }
+
+ /**
+ * Convert a collection of data nodes to a list of data maps.
+ *
+ * @param anchor the anchor
+ * @param dataNodes the data nodes to convert
+ * @return a list of maps representing the data nodes
+ */
+ public List<Map<String, Object>> toDataMaps(final Anchor anchor, final Collection<DataNode> dataNodes) {
+ final List<Map<String, Object>> dataMaps = new ArrayList<>(dataNodes.size());
+ for (final DataNode dataNode : dataNodes) {
+ final String prefix = prefixResolver.getPrefix(anchor, dataNode.getXpath());
+ final Map<String, Object> dataMap = DataMapUtils.toDataMapWithIdentifier(dataNode, prefix);
+ dataMaps.add(dataMap);
+ }
+ return dataMaps;
+ }
+
+ /**
+ * Convert a collection of data nodes (belonging to multiple anchors) to a list of data maps.
+ *
+ * @param dataspaceName the name dataspace name
+ * @param dataNodes the data nodes to convert
+ * @return a list of maps representing the data nodes
+ */
+ public List<Map<String, Object>> toDataMaps(final String dataspaceName, final Collection<DataNode> dataNodes) {
+ final List<Map<String, Object>> dataNodesAsMaps = new ArrayList<>(dataNodes.size());
+ final Map<String, List<DataNode>> dataNodesPerAnchor = groupDataNodesPerAnchor(dataNodes);
+ for (final Map.Entry<String, List<DataNode>> dataNodesPerAnchorEntry : dataNodesPerAnchor.entrySet()) {
+ final String anchorName = dataNodesPerAnchorEntry.getKey();
+ final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
+ final DataNode dataNode = dataNodesPerAnchorEntry.getValue().get(0);
+ final String prefix = prefixResolver.getPrefix(anchor, dataNode.getXpath());
+ final Map<String, Object> dataNodeAsMap = DataMapUtils.toDataMapWithIdentifierAndAnchor(
+ dataNodesPerAnchorEntry.getValue(), anchorName, prefix);
+ dataNodesAsMaps.add(dataNodeAsMap);
+ }
+ return dataNodesAsMaps;
+ }
+
+ /**
+ * Convert a collection of data nodes to a data map.
+ *
+ * @param anchor the anchor
+ * @param dataNodes the data nodes to convert
+ * @return a map representing the data nodes
+ */
+ public Map<String, Object> toFlatDataMap(final Anchor anchor, final Collection<DataNode> dataNodes) {
+ final List<Map<String, Object>> dataNodesAsMaps = toDataMaps(anchor, dataNodes);
+ return flattenDataNodesMaps(dataNodesAsMaps);
+ }
+
+ private Map<String, Object> flattenDataNodesMaps(final List<Map<String, Object>> dataNodesAsMaps) {
+ final Map<String, Object> dataNodesAsFlatMap = new HashMap<>();
+ for (final Map<String, Object> dataNodeAsMap : dataNodesAsMaps) {
+ dataNodesAsFlatMap.putAll(dataNodeAsMap);
+ }
+ return dataNodesAsFlatMap;
+ }
+
+ private static Map<String, List<DataNode>> groupDataNodesPerAnchor(final Collection<DataNode> dataNodes) {
+ return dataNodes.stream().collect(Collectors.groupingBy(DataNode::getAnchorName));
+ }
+
+}
diff --git a/cps-service/src/main/java/org/onap/cps/utils/PrefixResolver.java b/cps-service/src/main/java/org/onap/cps/utils/PrefixResolver.java
index bd348a25d1..e59029f916 100644
--- a/cps-service/src/main/java/org/onap/cps/utils/PrefixResolver.java
+++ b/cps-service/src/main/java/org/onap/cps/utils/PrefixResolver.java
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2022-2024 Nordix Foundation.
+ * Copyright (C) 2022-2025 Nordix Foundation.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -47,25 +47,29 @@ public class PrefixResolver {
* @return the prefix of the module the top level element of given xpath
*/
public String getPrefix(final Anchor anchor, final String xpath) {
+ return getPrefix(anchor.getDataspaceName(), anchor.getSchemaSetName(), xpath);
+ }
+
+ private String getPrefix(final String dataspaceName, final String schemaSetName, final String xpath) {
final CpsPathQuery cpsPathQuery = CpsPathUtil.getCpsPathQuery(xpath);
if (cpsPathQuery.getCpsPathPrefixType() != CpsPathPrefixType.ABSOLUTE) {
return "";
}
- final String topLevelContainerName = cpsPathQuery.getContainerNames().get(0);
+ final String topLevelContainerName = cpsPathQuery.getContainerNames().get(0);
final YangTextSchemaSourceSet yangTextSchemaSourceSet =
- yangTextSchemaSourceSetCache.get(anchor.getDataspaceName(), anchor.getSchemaSetName());
+ yangTextSchemaSourceSetCache.get(dataspaceName, schemaSetName);
final SchemaContext schemaContext = yangTextSchemaSourceSet.getSchemaContext();
return schemaContext.getChildNodes().stream()
- .filter(DataNodeContainer.class::isInstance)
- .map(SchemaNode::getQName)
- .filter(qname -> qname.getLocalName().equals(topLevelContainerName))
- .findFirst()
- .map(QName::getModule)
- .flatMap(schemaContext::findModule)
- .map(Module::getPrefix)
- .orElse("");
+ .filter(DataNodeContainer.class::isInstance)
+ .map(SchemaNode::getQName)
+ .filter(qname -> qname.getLocalName().equals(topLevelContainerName))
+ .findFirst()
+ .map(QName::getModule)
+ .flatMap(schemaContext::findModule)
+ .map(Module::getPrefix)
+ .orElse("");
}
}
diff --git a/cps-service/src/test/groovy/org/onap/cps/api/parameters/FetchDescendantsOptionSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/parameters/FetchDescendantsOptionSpec.groovy
index 126e5b197b..508178b419 100644
--- a/cps-service/src/test/groovy/org/onap/cps/api/parameters/FetchDescendantsOptionSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/api/parameters/FetchDescendantsOptionSpec.groovy
@@ -1,6 +1,6 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2022-2023 Nordix Foundation
+ * Copyright (C) 2022-2025 Nordix Foundation
* Modifications Copyright (C) 2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -25,6 +25,10 @@ package org.onap.cps.api.parameters
import org.onap.cps.api.exceptions.DataValidationException
import spock.lang.Specification
+import static org.onap.cps.api.parameters.FetchDescendantsOption.DIRECT_CHILDREN_ONLY
+import static org.onap.cps.api.parameters.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
+import static org.onap.cps.api.parameters.FetchDescendantsOption.OMIT_DESCENDANTS
+
class FetchDescendantsOptionSpec extends Specification {
def 'Has next descendant for fetch descendant option: #scenario'() {
@@ -105,11 +109,22 @@ class FetchDescendantsOptionSpec extends Specification {
expect: 'each fetch descendant option has the correct String value'
assert fetchDescendantsOption.toString() == expectedStringValue
where: 'the following option is used'
- fetchDescendantsOption || expectedStringValue
- FetchDescendantsOption.OMIT_DESCENDANTS || 'OmitDescendants'
- FetchDescendantsOption.DIRECT_CHILDREN_ONLY || 'DirectChildrenOnly'
- FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS || 'IncludeAllDescendants'
- new FetchDescendantsOption(2) || 'Depth=2'
+ fetchDescendantsOption || expectedStringValue
+ OMIT_DESCENDANTS || 'OmitDescendants'
+ DIRECT_CHILDREN_ONLY || 'DirectChildrenOnly'
+ INCLUDE_ALL_DESCENDANTS || 'IncludeAllDescendants'
+ new FetchDescendantsOption(2) || 'Depth=2'
+ }
+
+ def 'Convert include-descendants boolean to fetch descendants option with : #includeDescendants'() {
+ when: 'convert boolean #includeDescendants'
+ def result = FetchDescendantsOption.getFetchDescendantsOption(includeDescendants)
+ then: 'result is the expected option'
+ assert result == expectedFetchDescendantsOption
+ where: 'following parameters are used'
+ includeDescendants || expectedFetchDescendantsOption
+ true || INCLUDE_ALL_DESCENDANTS
+ false || OMIT_DESCENDANTS
}
}
diff --git a/cps-service/src/test/groovy/org/onap/cps/impl/CpsAnchorServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/impl/CpsAnchorServiceImplSpec.groovy
index d78c8bb47f..a21a17fabd 100644
--- a/cps-service/src/test/groovy/org/onap/cps/impl/CpsAnchorServiceImplSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/impl/CpsAnchorServiceImplSpec.groovy
@@ -20,7 +20,6 @@
package org.onap.cps.impl
-
import org.onap.cps.utils.CpsValidator
import org.onap.cps.spi.CpsAdminPersistenceService
import org.onap.cps.spi.CpsDataPersistenceService
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..967bcc0aa0 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
@@ -1,9 +1,9 @@
/*
* ============LICENSE_START=======================================================
- * Copyright (C) 2021-2024 Nordix Foundation
+ * Copyright (C) 2021-2025 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.
@@ -30,17 +30,18 @@ import com.fasterxml.jackson.databind.ObjectMapper
import org.onap.cps.TestUtils
import org.onap.cps.api.CpsAnchorService
import org.onap.cps.api.CpsDeltaService
-import org.onap.cps.events.CpsDataUpdateEventsService
-import org.onap.cps.utils.CpsValidator
-import org.onap.cps.spi.CpsDataPersistenceService
-import org.onap.cps.api.parameters.FetchDescendantsOption
import org.onap.cps.api.exceptions.ConcurrencyException
import org.onap.cps.api.exceptions.DataNodeNotFoundExceptionBatch
import org.onap.cps.api.exceptions.DataValidationException
import org.onap.cps.api.exceptions.SessionManagerException
import org.onap.cps.api.exceptions.SessionTimeoutException
import org.onap.cps.api.model.Anchor
+import org.onap.cps.api.parameters.FetchDescendantsOption
+import org.onap.cps.events.CpsDataUpdateEventsService
+import org.onap.cps.spi.CpsDataPersistenceService
import org.onap.cps.utils.ContentType
+import org.onap.cps.utils.CpsValidator
+import org.onap.cps.utils.DataMapper
import org.onap.cps.utils.JsonObjectMapper
import org.onap.cps.utils.PrefixResolver
import org.onap.cps.utils.YangParser
@@ -68,9 +69,11 @@ class CpsDataServiceImplSpec extends Specification {
def mockDataUpdateEventsService = Mock(CpsDataUpdateEventsService)
def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
def mockPrefixResolver = Mock(PrefixResolver)
+ def dataMapper = new DataMapper(mockCpsAnchorService, mockPrefixResolver)
+ def dataNodeFactory = new DataNodeFactoryImpl(yangParser)
def objectUnderTest = new CpsDataServiceImpl(mockCpsDataPersistenceService, mockDataUpdateEventsService, mockCpsAnchorService,
- mockCpsValidator, yangParser, mockCpsDeltaService, jsonObjectMapper, mockPrefixResolver)
+ dataNodeFactory, mockCpsValidator, yangParser, mockCpsDeltaService, dataMapper, jsonObjectMapper)
def logger = (Logger) LoggerFactory.getLogger(objectUnderTest.class)
def loggingListAppender
@@ -107,8 +110,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 +135,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 +143,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 +164,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 +174,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 +192,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 +206,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/CpsFacadeImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/impl/CpsFacadeImplSpec.groovy
new file mode 100644
index 0000000000..c754970518
--- /dev/null
+++ b/cps-service/src/test/groovy/org/onap/cps/impl/CpsFacadeImplSpec.groovy
@@ -0,0 +1,114 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025 Nordix Foundation
+ * ================================================================================
+ * 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 org.onap.cps.api.CpsAnchorService
+import org.onap.cps.api.CpsDataService
+import org.onap.cps.api.CpsQueryService
+import org.onap.cps.api.model.DataNode
+import org.onap.cps.api.parameters.PaginationOption
+import org.onap.cps.utils.DataMapper
+import org.onap.cps.utils.PrefixResolver
+import spock.lang.Specification
+
+import static org.onap.cps.api.parameters.FetchDescendantsOption.OMIT_DESCENDANTS
+import static org.onap.cps.api.parameters.PaginationOption.NO_PAGINATION
+
+class CpsFacadeImplSpec extends Specification {
+
+ def mockCpsDataService = Mock(CpsDataService)
+ def mockCpsQueryService = Mock(CpsQueryService)
+ def mockCpsAnchorService = Mock(CpsAnchorService)
+ def mockPrefixResolver = Mock(PrefixResolver)
+ def dataMapper = new DataMapper(mockCpsAnchorService, mockPrefixResolver)
+
+ def myFetchDescendantsOption = OMIT_DESCENDANTS
+ def myPaginationOption = NO_PAGINATION
+
+ def objectUnderTest = new CpsFacadeImpl(mockCpsDataService, mockCpsQueryService , dataMapper)
+
+ def dataNode1 = new DataNode(xpath:'/path1', anchorName: 'my anchor')
+ def dataNode2 = new DataNode(xpath:'/path2', anchorName: 'my anchor')
+ def dataNode3 = new DataNode(xpath:'/path3', anchorName: 'other anchor')
+
+ def setup() {
+ mockCpsDataService.getDataNodes('my dataspace', 'my anchor', 'my path', myFetchDescendantsOption) >> [ dataNode1, dataNode2]
+ mockPrefixResolver.getPrefix(_, '/path1') >> 'prefix1'
+ mockPrefixResolver.getPrefix(_, '/path2') >> 'prefix2'
+ mockPrefixResolver.getPrefix(_, '/path3') >> 'prefix3'
+ }
+
+ def 'Get one data node.'() {
+ when: 'get data node by dataspace and anchor'
+ def result = objectUnderTest.getFirstDataNodeByAnchor('my dataspace', 'my anchor', 'my path', myFetchDescendantsOption)
+ then: 'only the first node (from the data service result) is returned'
+ assert result.size() == 1
+ assert result.keySet()[0] == 'prefix1:path1'
+ }
+
+ def 'Get multiple data nodes.'() {
+ when: 'get data node by dataspace and anchor'
+ def result = objectUnderTest.getDataNodesByAnchor('my dataspace', 'my anchor', 'my path', myFetchDescendantsOption)
+ then: 'all nodes (from the data service result) are returned'
+ assert result.size() == 2
+ assert result[0].keySet()[0] == 'prefix1:path1'
+ assert result[1].keySet()[0] == 'prefix2:path2'
+ }
+
+ def 'Execute anchor query.'() {
+ given: 'the cps query service returns two data nodes'
+ mockCpsQueryService.queryDataNodes('my dataspace', 'my anchor', 'my cps path', myFetchDescendantsOption) >> [ dataNode1, dataNode2]
+ when: 'get data node by dataspace and anchor'
+ def result = objectUnderTest.executeAnchorQuery('my dataspace', 'my anchor', 'my cps path', myFetchDescendantsOption)
+ then: 'all nodes (from the query service result) are returned'
+ assert result.size() == 2
+ assert result[0].keySet()[0] == 'prefix1:path1'
+ assert result[1].keySet()[0] == 'prefix2:path2'
+ }
+
+ def 'Execute dataspace query.'() {
+ given: 'the cps query service returns two data nodes (on two different anchors)'
+ mockCpsQueryService.queryDataNodesAcrossAnchors('my dataspace', 'my cps path', myFetchDescendantsOption, myPaginationOption) >> [ dataNode1, dataNode2, dataNode3 ]
+ when: 'get data node by dataspace and anchor'
+ def result = objectUnderTest.executeDataspaceQuery('my dataspace', 'my cps path', myFetchDescendantsOption, myPaginationOption)
+ then: 'all nodes (from the query service result) are returned, grouped by anchor'
+ assert result.size() == 2
+ assert result[0].toString() == '{anchorName=my anchor, dataNodes=[{prefix1:path1={}}, {prefix1:path2={}}]}'
+ assert result[1].toString() == '{anchorName=other anchor, dataNodes=[{prefix3:path3={}}]}'
+ }
+
+ def 'How many pages (anchors) could be in the output with #scenario.'() {
+ given: 'the query service says there are 10 anchors for the given query'
+ mockCpsQueryService.countAnchorsForDataspaceAndCpsPath('my dataspace', 'my cps path') >> 10
+ expect: 'the correct number of pages is returned'
+ assert objectUnderTest.countAnchorsInDataspaceQuery('my dataspace', 'my cps path', paginationOption) == expectedNumberOfPages
+ where: 'the following pagination options are used'
+ scenario | paginationOption || expectedNumberOfPages
+ 'no pagination' | NO_PAGINATION || 1
+ '1 anchor per page' | new PaginationOption(1,1) || 10
+ '1 anchor per page, start at 2' | new PaginationOption(2,1) || 10
+ '2 anchors per page' | new PaginationOption(1,2) || 5
+ '3 anchors per page' | new PaginationOption(1,3) || 4
+ '10 anchors per page' | new PaginationOption(1,10) || 1
+ '100 anchors per page' | new PaginationOption(1,100) || 1
+ }
+
+}
diff --git a/cps-service/src/test/groovy/org/onap/cps/impl/CpsNotificationServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/impl/CpsNotificationServiceImplSpec.groovy
index b7f06456c9..ab7853c8e6 100644
--- a/cps-service/src/test/groovy/org/onap/cps/impl/CpsNotificationServiceImplSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/impl/CpsNotificationServiceImplSpec.groovy
@@ -1,6 +1,7 @@
/*
* ============LICENSE_START=======================================================
* Copyright (C) 2025 TechMahindra Ltd.
+ * Modifications Copyright (C) 2025 Nordix Foundation
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,22 +22,22 @@
package org.onap.cps.impl
import com.fasterxml.jackson.databind.ObjectMapper
+import org.onap.cps.TestUtils
import org.onap.cps.api.CpsAnchorService
import org.onap.cps.api.exceptions.DataNodeNotFoundException
import org.onap.cps.api.exceptions.DataValidationException
import org.onap.cps.api.model.Anchor
-import org.onap.cps.api.parameters.FetchDescendantsOption;
+import org.onap.cps.api.parameters.FetchDescendantsOption
import org.onap.cps.spi.CpsDataPersistenceService
+import org.onap.cps.utils.DataMapper
import org.onap.cps.utils.JsonObjectMapper
import org.onap.cps.utils.PrefixResolver
import org.onap.cps.utils.YangParser
-import org.onap.cps.TestUtils
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.springframework.test.context.ContextConfiguration
-
import spock.lang.Specification
@ContextConfiguration(classes = [ObjectMapper, JsonObjectMapper])
@@ -53,9 +54,9 @@ class CpsNotificationServiceImplSpec extends Specification {
def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache)
def mockTimedYangTextSchemaSourceSetBuilder = Mock(TimedYangTextSchemaSourceSetBuilder)
def yangParser = new YangParser(new YangParserHelper(), mockYangTextSchemaSourceSetCache, mockTimedYangTextSchemaSourceSetBuilder)
- def mockPrefixResolver = Mock(PrefixResolver)
+ def dataMapper = new DataMapper(mockCpsAnchorService, Mock(PrefixResolver))
- def objectUnderTest = new CpsNotificationServiceImpl(mockCpsAnchorService, mockCpsDataPersistenceService, yangParser, mockPrefixResolver)
+ def objectUnderTest = new CpsNotificationServiceImpl(mockCpsAnchorService, mockCpsDataPersistenceService, yangParser, dataMapper)
def 'add notification subscription for list of dataspaces'() {
given: 'details for notification subscription and subscription root node xpath'
diff --git a/cps-service/src/test/groovy/org/onap/cps/impl/CpsQueryServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/impl/CpsQueryServiceImplSpec.groovy
index 9db4aa4c3e..d581727e40 100644
--- a/cps-service/src/test/groovy/org/onap/cps/impl/CpsQueryServiceImplSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/impl/CpsQueryServiceImplSpec.groovy
@@ -77,8 +77,8 @@ class CpsQueryServiceImplSpec extends Specification {
and: 'the CpsValidator is called on the dataspaceName, schemaSetName and anchorName'
1 * mockCpsValidator.validateNameCharacters(dataspaceName)
where: 'all fetch descendants options are supported'
- fetchDescendantsOption << [FetchDescendantsOption.OMIT_DESCENDANTS, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS,
- FetchDescendantsOption.DIRECT_CHILDREN_ONLY, new FetchDescendantsOption(10)]
+ fetchDescendantsOption << [FetchDescendantsOption.OMIT_DESCENDANTS, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS,
+ FetchDescendantsOption.DIRECT_CHILDREN_ONLY, new FetchDescendantsOption(10)]
}
def 'Query total anchors for dataspace and cps path.'() {
@@ -91,7 +91,7 @@ class CpsQueryServiceImplSpec extends Specification {
def 'Query data leaf.'() {
when: 'a query for a specific leaf is executed'
objectUnderTest.queryDataLeaf('some-dataspace', 'some-anchor', '/cps-path/@id', Object.class)
- then: 'solution is not implemented yet'
- 1 * mockCpsDataPersistenceService.queryDataLeaf('some-dataspace', 'some-anchor', '/cps-path/@id', Object.class)
+ then: 'the persistence service is called once with the correct parameters'
+ 1 * mockCpsDataPersistenceService.queryDataLeaf('some-dataspace', 'some-anchor', '/cps-path/@id', 0, Object.class)
}
}
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..893cce6687 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.
@@ -27,12 +27,13 @@ import com.fasterxml.jackson.databind.ObjectMapper
import org.onap.cps.TestUtils
import org.onap.cps.api.CpsAnchorService
import org.onap.cps.api.CpsDeltaService
+import org.onap.cps.api.model.Anchor
import org.onap.cps.events.CpsDataUpdateEventsService
-import org.onap.cps.utils.CpsValidator
import org.onap.cps.spi.CpsDataPersistenceService
import org.onap.cps.spi.CpsModulePersistenceService
-import org.onap.cps.api.model.Anchor
import org.onap.cps.utils.ContentType
+import org.onap.cps.utils.CpsValidator
+import org.onap.cps.utils.DataMapper
import org.onap.cps.utils.JsonObjectMapper
import org.onap.cps.utils.PrefixResolver
import org.onap.cps.utils.YangParser
@@ -42,23 +43,22 @@ import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
import spock.lang.Specification
class E2ENetworkSliceSpec extends Specification {
- def mockModuleStoreService = Mock(CpsModulePersistenceService)
- def mockDataStoreService = Mock(CpsDataPersistenceService)
+ def mockCpsModulePersistenceService = Mock(CpsModulePersistenceService)
+ def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService)
def mockCpsAnchorService = Mock(CpsAnchorService)
def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache)
def mockCpsValidator = Mock(CpsValidator)
def timedYangTextSchemaSourceSetBuilder = new TimedYangTextSchemaSourceSetBuilder()
def yangParser = new YangParser(new YangParserHelper(), mockYangTextSchemaSourceSetCache, timedYangTextSchemaSourceSetBuilder)
def mockCpsDeltaService = Mock(CpsDeltaService)
+ def dataMapper = new DataMapper(mockCpsAnchorService, Mock(PrefixResolver))
def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
- def mockPrefixResolver = Mock(PrefixResolver)
- def cpsModuleServiceImpl = new CpsModuleServiceImpl(mockModuleStoreService,
- mockYangTextSchemaSourceSetCache, mockCpsAnchorService, mockCpsValidator,timedYangTextSchemaSourceSetBuilder)
+ def cpsModuleServiceImpl = new CpsModuleServiceImpl(mockCpsModulePersistenceService, mockYangTextSchemaSourceSetCache, mockCpsAnchorService, mockCpsValidator,timedYangTextSchemaSourceSetBuilder)
def mockDataUpdateEventsService = Mock(CpsDataUpdateEventsService)
- def cpsDataServiceImpl = new CpsDataServiceImpl(mockDataStoreService, mockDataUpdateEventsService, mockCpsAnchorService, mockCpsValidator,
- yangParser, mockCpsDeltaService, jsonObjectMapper, mockPrefixResolver)
+ def dataNodeFactory = new DataNodeFactoryImpl(yangParser)
+ def cpsDataServiceImpl = new CpsDataServiceImpl(mockCpsDataPersistenceService, mockDataUpdateEventsService, mockCpsAnchorService, dataNodeFactory, mockCpsValidator, yangParser, mockCpsDeltaService, dataMapper, jsonObjectMapper)
def dataspaceName = 'someDataspace'
def anchorName = 'someAnchor'
def schemaSetName = 'someSchemaSet'
@@ -74,7 +74,7 @@ class E2ENetworkSliceSpec extends Specification {
when: 'Create schema set method is invoked'
cpsModuleServiceImpl.createSchemaSet(dataspaceName, schemaSetName, yangResourceContentPerName)
then: 'Parameters are validated and processing is delegated to persistence service'
- 1 * mockModuleStoreService.createSchemaSet(dataspaceName, schemaSetName, yangResourceContentPerName)
+ 1 * mockCpsModulePersistenceService.createSchemaSet(dataspaceName, schemaSetName, yangResourceContentPerName)
}
def 'E2E Coverage Area-Tracking Area & TA-Cell mapping model can be parsed by CPS.'() {
@@ -84,7 +84,7 @@ class E2ENetworkSliceSpec extends Specification {
when: 'Create schema set method is invoked'
cpsModuleServiceImpl.createSchemaSet(dataspaceName, schemaSetName, yangResourceContentPerName)
then: 'Parameters are validated and processing is delegated to persistence service'
- 1 * mockModuleStoreService.createSchemaSet(dataspaceName, schemaSetName, yangResourceContentPerName)
+ 1 * mockCpsModulePersistenceService.createSchemaSet(dataspaceName, schemaSetName, yangResourceContentPerName)
}
def 'E2E Coverage Area-Tracking Area & TA-Cell mapping data can be parsed by CPS.'() {
@@ -100,31 +100,28 @@ class E2ENetworkSliceSpec extends Specification {
new Anchor().builder().name(anchorName).schemaSetName(schemaSetName).dataspaceName(dataspaceName).build()
mockYangTextSchemaSourceSetCache.get(dataspaceName, schemaSetName) >>
YangTextSchemaSourceSetBuilder.of(yangResourceContentPerName)
- mockModuleStoreService.getYangSchemaResources(dataspaceName, schemaSetName) >> schemaContext
+ mockCpsModulePersistenceService.getYangSchemaResources(dataspaceName, schemaSetName) >> schemaContext
when: 'saveData method is invoked'
cpsDataServiceImpl.saveData(dataspaceName, anchorName, jsonData, noTimestamp)
then: 'Parameters are validated and processing is delegated to persistence service'
- 1 * mockDataStoreService.storeDataNodes('someDataspace', 'someAnchor', _) >>
+ 1 * mockCpsDataPersistenceService.storeDataNodes('someDataspace', 'someAnchor', _) >>
{ args -> dataNodeStored = args[2]}
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]
- listOfTAForCoverageArea.xpath == '/ran-coverage-area/pLMNIdList[@mcc=\'310\' and @mnc=\'410\']/' +
- 'coverage-area[@coverageArea=\'Washington\']'
- listOfTAForCoverageArea.childDataNodes[0].leaves.get('nRTAC') == 234
+ listOfTAForCoverageArea.xpath == '/ran-coverage-area/pLMNIdList[@mcc=\'310\' and @mnc=\'410\']/coverage-area[@coverageArea=\'Washington\']'
+ assert listOfTAForCoverageArea.childDataNodes[0].leaves.get('nRTAC') == 234
and: 'list of cells in a tracking area are stored with correct xpath and child nodes '
def listOfCellsInTrackingArea = listOfTAForCoverageArea.childDataNodes[0]
- listOfCellsInTrackingArea.xpath == '/ran-coverage-area/pLMNIdList[@mcc=\'310\' and @mnc=\'410\']/' +
- 'coverage-area[@coverageArea=\'Washington\']/coverageAreaTAList[@nRTAC=\'234\']'
+ listOfCellsInTrackingArea.xpath == '/ran-coverage-area/pLMNIdList[@mcc=\'310\' and @mnc=\'410\']/coverage-area[@coverageArea=\'Washington\']/coverageAreaTAList[@nRTAC=\'234\']'
listOfCellsInTrackingArea.childDataNodes[0].leaves.get('cellLocalId') == 15709
}
def 'E2E Coverage Area-Tracking Area & TA-Cell mapping data can be parsed for RAN inventory.'() {
def dataNodeStored
given: 'valid yang resource as name-to-content map'
- def yangResourceContentPerName = TestUtils.getYangResourcesAsMap(
- 'e2e/basic/cps-ran-inventory@2021-01-28.yang')
+ def yangResourceContentPerName = TestUtils.getYangResourcesAsMap('e2e/basic/cps-ran-inventory@2021-01-28.yang')
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceContentPerName).getSchemaContext()
and : 'a valid json is provided for the model'
def jsonData = TestUtils.getResourceFileContent('e2e/basic/cps-ran-inventory-data.json')
@@ -132,12 +129,11 @@ class E2ENetworkSliceSpec extends Specification {
mockCpsAnchorService.getAnchor('someDataspace', 'someAnchor') >>
new Anchor().builder().name('someAnchor').schemaSetName('someSchemaSet').dataspaceName(dataspaceName).build()
mockYangTextSchemaSourceSetCache.get('someDataspace', 'someSchemaSet') >> YangTextSchemaSourceSetBuilder.of(yangResourceContentPerName)
- mockModuleStoreService.getYangSchemaResources('someDataspace', 'someSchemaSet') >> schemaContext
+ mockCpsModulePersistenceService.getYangSchemaResources('someDataspace', 'someSchemaSet') >> schemaContext
when: 'saveData method is invoked'
cpsDataServiceImpl.saveData('someDataspace', 'someAnchor', jsonData, noTimestamp)
then: 'parameters are validated and processing is delegated to persistence service'
- 1 * mockDataStoreService.storeDataNodes('someDataspace', 'someAnchor', _) >>
- { args -> dataNodeStored = args[2]}
+ 1 * mockCpsDataPersistenceService.storeDataNodes('someDataspace', 'someAnchor', _) >> { args -> dataNodeStored = args[2]}
and: 'the size of the tree is correct'
def cpsRanInventory = TestUtils.getFlattenMapByXpath(dataNodeStored[0])
assert cpsRanInventory.size() == 4
@@ -146,17 +142,16 @@ class E2ENetworkSliceSpec extends Specification {
def ranSlices = cpsRanInventory.get('/ran-inventory/ran-slices[@rannfnssiid=\'14559ead-f4fe-4c1c-a94c-8015fad3ea35\']')
def sliceProfilesList = cpsRanInventory.get('/ran-inventory/ran-slices[@rannfnssiid=\'14559ead-f4fe-4c1c-a94c-8015fad3ea35\']/sliceProfilesList[@sliceProfileId=\'f33a9dd8-ae51-4acf-8073-c9390c25f6f1\']')
def pLMNIdList = cpsRanInventory.get('/ran-inventory/ran-slices[@rannfnssiid=\'14559ead-f4fe-4c1c-a94c-8015fad3ea35\']/sliceProfilesList[@sliceProfileId=\'f33a9dd8-ae51-4acf-8073-c9390c25f6f1\']/pLMNIdList[@mcc=\'310\' and @mnc=\'410\']')
- ranInventory.getChildDataNodes().size() == 1
- ranInventory.getChildDataNodes().find( {it.xpath == ranSlices.xpath})
+ assert ranInventory.getChildDataNodes().size() == 1
+ assert ranInventory.getChildDataNodes().find( {it.xpath == ranSlices.xpath})
and: 'ranSlices contains the correct child node'
- ranSlices.getChildDataNodes().size() == 1
- ranSlices.getChildDataNodes().find( {it.xpath == sliceProfilesList.xpath})
+ assert ranSlices.getChildDataNodes().size() == 1
+ assert ranSlices.getChildDataNodes().find( {it.xpath == sliceProfilesList.xpath})
and: 'sliceProfilesList contains the correct child node'
- sliceProfilesList.getChildDataNodes().size() == 1
- sliceProfilesList.getChildDataNodes().find( {it.xpath == pLMNIdList.xpath})
+ assert sliceProfilesList.getChildDataNodes().size() == 1
+ assert sliceProfilesList.getChildDataNodes().find( {it.xpath == pLMNIdList.xpath})
and: 'pLMNIdList contains no children'
- pLMNIdList.getChildDataNodes().size() == 0
-
+ assert pLMNIdList.getChildDataNodes().size() == 0
}
def 'E2E RAN Schema Model.'(){