aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorarpitsingh <as00745003@techmahindra.com>2023-01-09 19:53:10 +0530
committerArpit Singh <as00745003@techmahindra.com>2023-02-17 11:20:35 +0000
commit0bd192ca12ac2f768e44d0d3482785c79a881904 (patch)
treea91a96325ecaa38adaa4b9420e4655b6cbea85a3
parent9575b84ab4e2db885d8761a98eaae9ff3a06aa81 (diff)
CPS-1401 Implement V2 of GET Data Node API
- Modified the GET Data Node API so it returns all the data nodes when xpath set to root "/" - Fragment Repository now returns a collection of Fragment Entities - Instead of returning only the first Fragment Entity now all fragment entities are returned when xpath is set to root - The Fragemnt Entities are further processed to a Collection of Data Nodes. As opposed to singular Data Node in current implementation. - Finally the DataRestController also returns a Collection of Data Nodes when xpath is set to root and valid data is present - Response body changed from JSON object to JSON Array. - Exception handling for invalid xpath and non-existing xpath is now done separately at persistence layer. - Refactored code against CPS-1422 - Deprecated getDataNode method from Service and Persistence layer - Modified V1 of Get Data Node API to use the getDataNodes method and get the first data node from the collection returned. - Modified NCMP to use getDataNodes method - NCMP still does not support multiple data nodes. It retrieves the first data node from the collection returned by getDataNodes Signed-off-by: arpitsingh <as00745003@techmahindra.com> Change-Id: I494a5740a53f65376d135fcb9f1e2e8900a2803e
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceImpl.java14
-rwxr-xr-xcps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java5
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java4
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CmHandleQueriesImpl.java5
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java7
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImpl.java18
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceSpec.groovy11
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy11
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy12
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CmHandleQueriesImplSpec.groovy9
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImplSpec.groovy16
-rw-r--r--cps-rest/docs/openapi/cpsDataV2.yml2
-rwxr-xr-xcps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java31
-rwxr-xr-xcps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy105
-rw-r--r--cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java25
-rwxr-xr-xcps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy126
-rw-r--r--cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy15
-rw-r--r--cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistenceSpecBase.groovy20
-rw-r--r--cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServicePerfTest.groovy6
-rwxr-xr-xcps-ri/src/test/resources/data/fragment.sql13
-rw-r--r--cps-service/src/main/java/org/onap/cps/api/CpsDataService.java38
-rwxr-xr-xcps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java20
-rw-r--r--cps-service/src/main/java/org/onap/cps/notification/CpsDataUpdatedEventFactory.java5
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java35
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy28
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdateEventFactorySpec.groovy5
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/CpsPersistenceSpec.groovy5
27 files changed, 355 insertions, 236 deletions
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceImpl.java
index b67ae0c19e..a98c6008cf 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceImpl.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceImpl.java
@@ -1,6 +1,7 @@
/*
* ============LICENSE_START=======================================================
* Copyright (C) 2022 Nordix Foundation
+ * Modifications Copyright (C) 2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -217,8 +218,8 @@ public class NetworkCmProxyCmHandlerQueryServiceImpl implements NetworkCmProxyCm
final Map<String, NcmpServiceCmHandle> queryResult = new HashMap<>(cmHandleIdsByModuleName.size());
if (previousQueryResult == NO_QUERY_TO_EXECUTE) {
cmHandleIdsByModuleName.forEach(cmHandleId ->
- queryResult.put(cmHandleId, createNcmpServiceCmHandle(
- inventoryPersistence.getDataNode("/dmi-registry/cm-handles[@id='" + cmHandleId + "']")))
+ queryResult.put(cmHandleId, createNcmpServiceCmHandle(inventoryPersistence
+ .getDataNode("/dmi-registry/cm-handles[@id='" + cmHandleId + "']").iterator().next()))
);
return queryResult;
}
@@ -298,13 +299,14 @@ public class NetworkCmProxyCmHandlerQueryServiceImpl implements NetworkCmProxyCm
}
private Set<NcmpServiceCmHandle> getAllCmHandles() {
- return inventoryPersistence.getDataNode("/dmi-registry")
- .getChildDataNodes().stream().map(this::createNcmpServiceCmHandle).collect(Collectors.toSet());
+ final DataNode dataNode = inventoryPersistence.getDataNode("/dmi-registry").iterator().next();
+ return dataNode.getChildDataNodes().stream().map(this::createNcmpServiceCmHandle).collect(Collectors.toSet());
}
private Set<String> getAllCmHandleIds() {
- return inventoryPersistence.getDataNode("/dmi-registry", FETCH_DIRECT_CHILDREN_ONLY)
- .getChildDataNodes().stream().map(dataNode -> dataNode.getLeaves().get("id").toString())
+ final DataNode dataNodes = inventoryPersistence.getDataNode("/dmi-registry", FETCH_DIRECT_CHILDREN_ONLY)
+ .iterator().next();
+ return dataNodes.getChildDataNodes().stream().map(dataNode -> dataNode.getLeaves().get("id").toString())
.collect(Collectors.toSet());
}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java
index e71b72ab86..d5500b8d7a 100755
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java
@@ -4,6 +4,7 @@
* Modifications Copyright (C) 2021-2023 Nordix Foundation
* Modifications Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2021-2022 Bell Canada
+ * Modifications Copyright (C) 2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -131,8 +132,8 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
public Object getResourceDataOperational(final String cmHandleId,
final String resourceIdentifier,
final FetchDescendantsOption fetchDescendantsOption) {
- return cpsDataService.getDataNode(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId, resourceIdentifier,
- fetchDescendantsOption);
+ return cpsDataService.getDataNodes(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId, resourceIdentifier,
+ fetchDescendantsOption).iterator().next();
}
@Override
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java
index f39c2f9b60..bbb2c0f56f 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java
@@ -2,6 +2,7 @@
* ============LICENSE_START=======================================================
* Copyright (C) 2022 Nordix Foundation
* Modifications Copyright (C) 2022 Bell Canada
+ * Modifications Copyright (C) 2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -66,7 +67,8 @@ public class NetworkCmProxyDataServicePropertyHandler {
for (final NcmpServiceCmHandle ncmpServiceCmHandle : ncmpServiceCmHandles) {
final String cmHandleId = ncmpServiceCmHandle.getCmHandleId();
try {
- final DataNode existingCmHandleDataNode = inventoryPersistence.getCmHandleDataNode(cmHandleId);
+ final DataNode existingCmHandleDataNode = inventoryPersistence.getCmHandleDataNode(cmHandleId)
+ .iterator().next();
processUpdates(existingCmHandleDataNode, ncmpServiceCmHandle);
cmHandleRegistrationResponses.add(CmHandleRegistrationResponse.createSuccessResponse(cmHandleId));
} catch (final DataNodeNotFoundException e) {
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CmHandleQueriesImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CmHandleQueriesImpl.java
index bda0a728b4..0f86cb7be6 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CmHandleQueriesImpl.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CmHandleQueriesImpl.java
@@ -1,6 +1,7 @@
/*
* ============LICENSE_START=======================================================
* Copyright (C) 2022 Nordix Foundation
+ * Modifications Copyright (C) 2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -174,8 +175,8 @@ public class CmHandleQueriesImpl implements CmHandleQueries {
private DataNode getCmHandleState(final String cmHandleId) {
final String xpath = "/dmi-registry/cm-handles[@id='" + cmHandleId + "']/state";
- return cpsDataPersistenceService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
- xpath, OMIT_DESCENDANTS);
+ return cpsDataPersistenceService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+ xpath, OMIT_DESCENDANTS).iterator().next();
}
}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java
index 10227cf26f..d22cf0e16a 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java
@@ -1,6 +1,7 @@
/*
* ============LICENSE_START=======================================================
* Copyright (C) 2022-2023 Nordix Foundation
+ * Modifications Copyright (C) 2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -119,7 +120,7 @@ public interface InventoryPersistence {
* @param xpath xpath
* @return data node
*/
- DataNode getDataNode(String xpath);
+ Collection<DataNode> getDataNode(String xpath);
/**
* Get data node via xpath.
@@ -128,7 +129,7 @@ public interface InventoryPersistence {
* @param fetchDescendantsOption fetch descendants option
* @return data node
*/
- DataNode getDataNode(String xpath, FetchDescendantsOption fetchDescendantsOption);
+ Collection<DataNode> getDataNode(String xpath, FetchDescendantsOption fetchDescendantsOption);
/**
* Get collection of data nodes via xpaths.
@@ -153,7 +154,7 @@ public interface InventoryPersistence {
* @param cmHandleId cmHandle ID
* @return data node
*/
- DataNode getCmHandleDataNode(String cmHandleId);
+ Collection<DataNode> getCmHandleDataNode(String cmHandleId);
/**
* Get collection of data nodes of given cm handles.
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImpl.java
index 4d1202b065..1c799c6b26 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImpl.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImpl.java
@@ -2,6 +2,7 @@
* ============LICENSE_START=======================================================
* Copyright (C) 2022-2023 Nordix Foundation
* Modifications Copyright (C) 2022 Bell Canada
+ * Modifications Copyright (C) 2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -73,9 +74,9 @@ public class InventoryPersistenceImpl implements InventoryPersistence {
@Override
public CompositeState getCmHandleState(final String cmHandleId) {
- final DataNode stateAsDataNode = cpsDataService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+ final DataNode stateAsDataNode = cpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
createCmHandleXPath(cmHandleId) + "/state",
- FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS);
+ FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS).iterator().next();
cpsValidator.validateNameCharacters(cmHandleId);
return new CompositeStateBuilder().fromDataNode(stateAsDataNode).build();
}
@@ -101,7 +102,8 @@ public class InventoryPersistenceImpl implements InventoryPersistence {
@Override
public YangModelCmHandle getYangModelCmHandle(final String cmHandleId) {
cpsValidator.validateNameCharacters(cmHandleId);
- return YangDataConverter.convertCmHandleToYangModel(getCmHandleDataNode(cmHandleId), cmHandleId);
+ final DataNode dataNode = getCmHandleDataNode(cmHandleId).iterator().next();
+ return YangDataConverter.convertCmHandleToYangModel(dataNode, cmHandleId);
}
@Override
@@ -169,15 +171,15 @@ public class InventoryPersistenceImpl implements InventoryPersistence {
@Override
@Timed(value = "cps.ncmp.inventory.persistence.datanode.get",
description = "Time taken to get a data node (from ncmp dmi registry)")
- public DataNode getDataNode(final String xpath) {
+ public Collection<DataNode> getDataNode(final String xpath) {
return getDataNode(xpath, INCLUDE_ALL_DESCENDANTS);
}
@Override
@Timed(value = "cps.ncmp.inventory.persistence.datanode.get",
description = "Time taken to get a data node (from ncmp dmi registry)")
- public DataNode getDataNode(final String xpath, final FetchDescendantsOption fetchDescendantsOption) {
- return cpsDataService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+ public Collection<DataNode> getDataNode(final String xpath, final FetchDescendantsOption fetchDescendantsOption) {
+ return cpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
xpath, fetchDescendantsOption);
}
@@ -189,12 +191,12 @@ public class InventoryPersistenceImpl implements InventoryPersistence {
@Override
public Collection<DataNode> getDataNodes(final Collection<String> xpaths,
final FetchDescendantsOption fetchDescendantsOption) {
- return cpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+ return cpsDataService.getDataNodesForMultipleXpaths(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
xpaths, fetchDescendantsOption);
}
@Override
- public DataNode getCmHandleDataNode(final String cmHandleId) {
+ public Collection<DataNode> getCmHandleDataNode(final String cmHandleId) {
return this.getDataNode(createCmHandleXPath(cmHandleId));
}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceSpec.groovy
index 05856d0ea8..5cd702a6b9 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceSpec.groovy
@@ -1,6 +1,7 @@
/*
* ============LICENSE_START=======================================================
* Copyright (C) 2022 Nordix Foundation
+ * Modifications Copyright (C) 2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -40,7 +41,7 @@ class NetworkCmProxyCmHandlerQueryServiceSpec extends Specification {
def partiallyMockedCmHandleQueries = Spy(CmHandleQueriesImpl)
def mockInventoryPersistence = Mock(InventoryPersistence)
- def static someCmHandleDataNode = new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'some-cmhandle-id\']', leaves: ['id':'some-cmhandle-id'])
+ def static someCmHandleDataNode = [new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'some-cmhandle-id\']', leaves: ['id':'some-cmhandle-id'])]
def dmiRegistry = new DataNode(xpath: '/dmi-registry', childDataNodes: createDataNodeList(['PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4']))
static def queryResultCmHandleMap = createCmHandleMap(['H1', 'H2'])
@@ -136,7 +137,7 @@ class NetworkCmProxyCmHandlerQueryServiceSpec extends Specification {
and: 'cmHandles are returned from the module names query'
mockInventoryPersistence.getCmHandleIdsWithGivenModules(['some-module-name']) >> anchorsForModuleQuery
and: 'cmHandleQueries returns a datanode result'
- 2 * cmHandleQueries.queryCmHandleDataNodesByCpsPath(*_) >> [someCmHandleDataNode]
+ 2 * cmHandleQueries.queryCmHandleDataNodesByCpsPath(*_) >> someCmHandleDataNode
when: 'the query is executed for both cm handle ids and details'
def returnedCmHandlesJustIds = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters)
def returnedCmHandlesWithData = objectUnderTest.queryCmHandles(cmHandleQueryParameters)
@@ -157,9 +158,9 @@ class NetworkCmProxyCmHandlerQueryServiceSpec extends Specification {
given: 'We use an empty query'
def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
and: 'the inventory persistence returns the dmi registry datanode with just ids'
- mockInventoryPersistence.getDataNode("/dmi-registry", FetchDescendantsOption.FETCH_DIRECT_CHILDREN_ONLY) >> dmiRegistry
+ mockInventoryPersistence.getDataNode("/dmi-registry", FetchDescendantsOption.FETCH_DIRECT_CHILDREN_ONLY) >> [dmiRegistry]
and: 'the inventory persistence returns the dmi registry datanode with data'
- mockInventoryPersistence.getDataNode("/dmi-registry") >> dmiRegistry
+ mockInventoryPersistence.getDataNode("/dmi-registry") >> [dmiRegistry]
when: 'the query is executed for both cm handle ids and details'
def returnedCmHandlesJustIds = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters)
def returnedCmHandlesWithData = objectUnderTest.queryCmHandles(cmHandleQueryParameters)
@@ -173,7 +174,7 @@ class NetworkCmProxyCmHandlerQueryServiceSpec extends Specification {
given: 'We query without any parameters'
def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
and: 'the inventoryPersistence returns all four CmHandleIds'
- mockInventoryPersistence.getDataNode(*_) >> dmiRegistry
+ mockInventoryPersistence.getDataNode(*_) >> [dmiRegistry]
when: 'the query executed'
def resultSet = objectUnderTest.queryCmHandleIdsForInventory(cmHandleQueryParameters)
then: 'the size of the result list equals the size of all cmHandleIds.'
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy
index 578f7f31c7..fe6fa32ae3 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy
@@ -3,6 +3,7 @@
* Copyright (C) 2021-2022 Nordix Foundation
* Modifications Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2021-2022 Bell Canada
+ * Modifications Copyright (C) 2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -89,11 +90,11 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
def cmHandleXPath = "/dmi-registry/cm-handles[@id='testCmHandle']"
- def dataNode = new DataNode(leaves: ['id': 'some-cm-handle', 'dmi-service-name': 'testDmiService'])
+ def dataNode = [new DataNode(leaves: ['id': 'some-cm-handle', 'dmi-service-name': 'testDmiService'])]
def 'Write resource data for pass-through running from DMI using POST.'() {
given: 'cpsDataService returns valid datanode'
- mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
+ mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
when: 'write resource data is called'
objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
@@ -107,7 +108,7 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
def 'Get resource data for pass-through operational from DMI.'() {
given: 'get data node is called'
- mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
+ mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
and: 'get resource data from DMI is called'
mockDmiDataOperations.getResourceDataFromDmi(
@@ -129,7 +130,7 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
def 'Get resource data for pass-through running from DMI.'() {
given: 'cpsDataService returns valid data node'
- mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
+ mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
and: 'DMI returns valid response and data'
mockDmiDataOperations.getResourceDataFromDmi('testCmHandle',
@@ -233,7 +234,7 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
def 'Update resource data for pass-through running from dmi using POST #scenario DMI properties.'() {
given: 'cpsDataService returns valid datanode'
- mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
+ mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
when: 'get resource data is called'
objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy
index 64461fa7e2..0df61f4e0b 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy
@@ -2,6 +2,7 @@
* ============LICENSE_START=======================================================
* Copyright (C) 2022 Nordix Foundation
* Modifications Copyright (C) 2022 Bell Canada
+ * Modifications Copyright (C) 2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -47,11 +48,11 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification {
new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/additional-properties[@name='additionalProp2']").withLeaves(['name': 'additionalProp2', 'value': 'additionalValue2']).build(),
new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/public-properties[@name='publicProp3']").withLeaves(['name': 'publicProp3', 'value': 'publicValue3']).build(),
new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/public-properties[@name='publicProp4']").withLeaves(['name': 'publicProp4', 'value': 'publicValue4']).build()]
- def static cmHandleDataNode = new DataNode(xpath: cmHandleXpath, childDataNodes: propertyDataNodes)
+ def static cmHandleDataNodeAsCollection = [new DataNode(xpath: cmHandleXpath, childDataNodes: propertyDataNodes)]
def 'Update CM Handle Public Properties: #scenario'() {
given: 'the CPS service return a CM handle'
- mockInventoryPersistence.getCmHandleDataNode(cmHandleId) >> cmHandleDataNode
+ mockInventoryPersistence.getCmHandleDataNode(cmHandleId) >> cmHandleDataNodeAsCollection
and: 'an update cm handle request with public properties updates'
def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: updatedPublicProperties)]
when: 'update data node leaves is called with the update request'
@@ -73,7 +74,7 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification {
def 'Update DMI Properties: #scenario'() {
given: 'the CPS service return a CM handle'
- mockInventoryPersistence.getCmHandleDataNode(cmHandleId) >> cmHandleDataNode
+ mockInventoryPersistence.getCmHandleDataNode(cmHandleId) >> cmHandleDataNodeAsCollection
and: 'an update cm handle request with DMI properties updates'
def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, dmiProperties: updatedDmiProperties)]
when: 'update data node leaves is called with the update request'
@@ -97,7 +98,7 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification {
def 'Update CM Handle Properties, remove all properties: #scenario'() {
given: 'the CPS service return a CM handle'
def cmHandleDataNode = new DataNode(xpath: cmHandleXpath, childDataNodes: originalPropertyDataNodes)
- mockInventoryPersistence.getCmHandleDataNode(cmHandleId) >> cmHandleDataNode
+ mockInventoryPersistence.getCmHandleDataNode(cmHandleId) >> [cmHandleDataNode]
and: 'an update cm handle request that removes all public properties(existing and non-existing)'
def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: ['publicProp3': null, 'publicProp4': null])]
when: 'update data node leaves is called with the update request'
@@ -145,7 +146,8 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification {
new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: ['publicProp1': "value"], dmiProperties: [:]),
new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: ['publicProp1': "value"], dmiProperties: [:])]
and: 'data node can be found for 1st and 3rd cm-handle but not for 2nd cm-handle'
- mockInventoryPersistence.getCmHandleDataNode(*_) >> cmHandleDataNode >> { throw new DataNodeNotFoundException('NCMP-Admin', 'ncmp-dmi-registry') } >> cmHandleDataNode
+ mockInventoryPersistence.getCmHandleDataNode(*_) >> cmHandleDataNodeAsCollection >> {
+ throw new DataNodeNotFoundException('NCMP-Admin', 'ncmp-dmi-registry') } >> cmHandleDataNodeAsCollection
when: 'update data node leaves is called using correct parameters'
def cmHandleResponseList = objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest)
then: 'response has 3 values'
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CmHandleQueriesImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CmHandleQueriesImplSpec.groovy
index 2800f5c248..771198e289 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CmHandleQueriesImplSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CmHandleQueriesImplSpec.groovy
@@ -1,6 +1,7 @@
/*
* ============LICENSE_START=======================================================
* Copyright (C) 2022 Nordix Foundation
+ * Modifications Copyright (C) 2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -120,8 +121,8 @@ class CmHandleQueriesImplSpec extends Specification {
given: 'a cm handle state to compare'
def cmHandleState = state
and: 'the persistence service returns a list of data nodes'
- cpsDataPersistenceService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
- '/dmi-registry/cm-handles[@id=\'some-cm-handle\']/state', OMIT_DESCENDANTS) >> new DataNode(leaves: ['cm-handle-state': 'READY'])
+ cpsDataPersistenceService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
+ '/dmi-registry/cm-handles[@id=\'some-cm-handle\']/state', OMIT_DESCENDANTS) >> [new DataNode(leaves: ['cm-handle-state': 'READY'])]
when: 'cm handles are compared by state'
def result = objectUnderTest.cmHandleHasState('some-cm-handle', cmHandleState)
then: 'the returned result matches the expected result from the persistence service'
@@ -136,8 +137,8 @@ class CmHandleQueriesImplSpec extends Specification {
given: 'a cm handle state to query'
def cmHandleState = CmHandleState.READY
and: 'cps data service returns a list of data nodes'
- cpsDataPersistenceService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
- '/dmi-registry/cm-handles[@id=\'some-cm-handle\']/state', OMIT_DESCENDANTS) >> new DataNode(leaves: ['cm-handle-state': 'READY'])
+ cpsDataPersistenceService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
+ '/dmi-registry/cm-handles[@id=\'some-cm-handle\']/state', OMIT_DESCENDANTS) >> [new DataNode(leaves: ['cm-handle-state': 'READY'])]
when: 'cm handles are fetched by state and id'
def result = objectUnderTest.getCmHandleState('some-cm-handle')
then: 'the returned result is a list of data nodes returned by cps data service'
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImplSpec.groovy
index 929ea84e36..4a98526b48 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImplSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImplSpec.groovy
@@ -2,6 +2,7 @@
* ============LICENSE_START=======================================================
* Copyright (C) 2022-2023 Nordix Foundation
* Modifications Copyright (C) 2022 Bell Canada
+ * Modifications Copyright (C) 2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,7 +40,6 @@ import spock.lang.Specification
import java.time.OffsetDateTime
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
-import java.util.stream.Collectors
import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NO_TIMESTAMP
import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
@@ -85,7 +85,7 @@ class InventoryPersistenceImplSpec extends Specification {
def "Retrieve CmHandle using datanode with #scenario."() {
given: 'the cps data service returns a data node from the DMI registry'
def dataNode = new DataNode(childDataNodes:childDataNodes, leaves: leaves)
- mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry', xpath, INCLUDE_ALL_DESCENDANTS) >> dataNode
+ mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry', xpath, INCLUDE_ALL_DESCENDANTS) >> [dataNode]
when: 'retrieving the yang modelled cm handle'
def result = objectUnderTest.getYangModelCmHandle(cmHandleId)
then: 'the result has the correct id and service names'
@@ -112,7 +112,7 @@ class InventoryPersistenceImplSpec extends Specification {
def "Handling missing service names as null."() {
given: 'the cps data service returns a data node from the DMI registry with empty child and leaf attributes'
def dataNode = new DataNode(childDataNodes:[], leaves: [:])
- mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry', xpath, INCLUDE_ALL_DESCENDANTS) >> dataNode
+ mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry', xpath, INCLUDE_ALL_DESCENDANTS) >> [dataNode]
when: 'retrieving the yang modelled cm handle'
def result = objectUnderTest.getYangModelCmHandle(cmHandleId)
then: 'the service names are returned as null'
@@ -126,7 +126,7 @@ class InventoryPersistenceImplSpec extends Specification {
def "Retrieve multiple YangModelCmHandles"() {
given: 'the cps data service returns 2 data nodes from the DMI registry'
def dataNodes = [new DataNode(xpath: xpath), new DataNode(xpath: xpath2)]
- mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry', [xpath, xpath2] , INCLUDE_ALL_DESCENDANTS) >> dataNodes
+ mockCpsDataService.getDataNodesForMultipleXpaths('NCMP-Admin', 'ncmp-dmi-registry', [xpath, xpath2] , INCLUDE_ALL_DESCENDANTS) >> dataNodes
when: 'retrieving the yang modelled cm handle'
def results = objectUnderTest.getYangModelCmHandles([cmHandleId, cmHandleId2])
then: 'verify both have returned and cmhandleIds are correct'
@@ -139,8 +139,8 @@ class InventoryPersistenceImplSpec extends Specification {
def cmHandleId = 'Some-Cm-Handle'
def dataNode = new DataNode(leaves: ['cm-handle-state': 'ADVISED'])
and: 'cps data service returns a valid data node'
- mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
- '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle\']/state', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
+ mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
+ '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle\']/state', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> [dataNode]
when: 'get cm handle state is invoked'
def result = objectUnderTest.getCmHandleState(cmHandleId)
then: 'result has returned the correct cm handle state'
@@ -251,7 +251,7 @@ class InventoryPersistenceImplSpec extends Specification {
when: 'the method to get data nodes is called'
objectUnderTest.getDataNode('sample xPath')
then: 'the data persistence service method to get data node is invoked once'
- 1 * mockCpsDataService.getDataNode('NCMP-Admin','ncmp-dmi-registry','sample xPath', INCLUDE_ALL_DESCENDANTS)
+ 1 * mockCpsDataService.getDataNodes('NCMP-Admin','ncmp-dmi-registry','sample xPath', INCLUDE_ALL_DESCENDANTS)
}
def 'Get cmHandle data node'() {
@@ -260,7 +260,7 @@ class InventoryPersistenceImplSpec extends Specification {
when: 'the method to get data nodes is called'
objectUnderTest.getCmHandleDataNode('sample cmHandleId')
then: 'the data persistence service method to get cmHandle data node is invoked once with expected xPath'
- 1 * mockCpsDataService.getDataNode('NCMP-Admin','ncmp-dmi-registry',expectedXPath, INCLUDE_ALL_DESCENDANTS)
+ 1 * mockCpsDataService.getDataNodes('NCMP-Admin','ncmp-dmi-registry',expectedXPath, INCLUDE_ALL_DESCENDANTS)
}
def 'Get CM handles that has given module names'() {
diff --git a/cps-rest/docs/openapi/cpsDataV2.yml b/cps-rest/docs/openapi/cpsDataV2.yml
index 61663ab3a8..ad0c299d70 100644
--- a/cps-rest/docs/openapi/cpsDataV2.yml
+++ b/cps-rest/docs/openapi/cpsDataV2.yml
@@ -46,4 +46,4 @@ nodeByDataspaceAndAnchor:
$ref: 'components.yml#/components/responses/Forbidden'
'500':
$ref: 'components.yml#/components/responses/InternalServerError'
- x-codegen-request-body-name: xpath
+ x-codegen-request-body-name: xpath \ No newline at end of file
diff --git a/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java b/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java
index 3a9c764bc6..80cfb8ce0b 100755
--- a/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java
+++ b/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java
@@ -3,7 +3,7 @@
* Copyright (C) 2020-2022 Bell Canada.
* Modifications Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2021-2022 Nordix Foundation
- * Modifications Copyright (C) 2023 TechMahindra Ltd.
+ * Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
* Modifications Copyright (C) 2022 Deutsche Telekom AG
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -26,6 +26,10 @@ package org.onap.cps.rest.controller;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
import javax.validation.ValidationException;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
@@ -97,21 +101,27 @@ public class DataRestController implements CpsDataApi {
final String anchorName, final String xpath, final Boolean includeDescendants) {
final FetchDescendantsOption fetchDescendantsOption = Boolean.TRUE.equals(includeDescendants)
? FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS : FetchDescendantsOption.OMIT_DESCENDANTS;
- final DataNode dataNode = cpsDataService.getDataNode(dataspaceName, anchorName, xpath,
- fetchDescendantsOption);
- final String prefix = prefixResolver.getPrefix(dataspaceName, anchorName, xpath);
+ final DataNode dataNode = cpsDataService.getDataNodes(dataspaceName, anchorName, xpath,
+ fetchDescendantsOption).iterator().next();
+ final String prefix = prefixResolver.getPrefix(dataspaceName, anchorName, dataNode.getXpath());
return new ResponseEntity<>(DataMapUtils.toDataMapWithIdentifier(dataNode, prefix), HttpStatus.OK);
}
@Override
public ResponseEntity<Object> getNodeByDataspaceAndAnchorV2(final String dataspaceName, final String anchorName,
- final String xpath, final String fetchDescendantsOptionAsString) {
+ final String xpath,
+ final String fetchDescendantsOptionAsString) {
final FetchDescendantsOption fetchDescendantsOption =
- FetchDescendantsOption.getFetchDescendantsOption(fetchDescendantsOptionAsString);
- final DataNode dataNode = cpsDataService.getDataNode(dataspaceName, anchorName, xpath,
- fetchDescendantsOption);
- final String prefix = prefixResolver.getPrefix(dataspaceName, anchorName, xpath);
- return new ResponseEntity<>(DataMapUtils.toDataMapWithIdentifier(dataNode, prefix), HttpStatus.OK);
+ FetchDescendantsOption.getFetchDescendantsOption(fetchDescendantsOptionAsString);
+ final Collection<DataNode> dataNodes = cpsDataService.getDataNodes(dataspaceName, anchorName, xpath,
+ fetchDescendantsOption);
+ final List<Map<String, Object>> dataMaps = new ArrayList<>(dataNodes.size());
+ for (final DataNode dataNode: dataNodes) {
+ final String prefix = prefixResolver.getPrefix(dataspaceName, anchorName, dataNode.getXpath());
+ final Map<String, Object> dataMap = DataMapUtils.toDataMapWithIdentifier(dataNode, prefix);
+ dataMaps.add(dataMap);
+ }
+ return new ResponseEntity<>(jsonObjectMapper.asJsonString(dataMaps), HttpStatus.OK);
}
@Override
@@ -162,5 +172,4 @@ public class DataRestController implements CpsDataApi {
String.format("observed-timestamp must be in '%s' format", ISO_TIMESTAMP_FORMAT));
}
}
-
}
diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
index 16d106ba60..d88a9cdf0b 100755
--- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
+++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
@@ -25,7 +25,9 @@
package org.onap.cps.rest.controller
import com.fasterxml.jackson.databind.ObjectMapper
+import groovy.json.JsonSlurper
import org.onap.cps.api.CpsDataService
+import org.onap.cps.spi.FetchDescendantsOption
import org.onap.cps.spi.model.DataNode
import org.onap.cps.spi.model.DataNodeBuilder
import org.onap.cps.utils.ContentType
@@ -68,7 +70,7 @@ class DataRestControllerSpec extends Specification {
@Value('${rest.api.cps-base-path}')
def basePath
- def dataNodeBaseEndpoint
+ def dataNodeBaseEndpointV1
def dataNodeBaseEndpointV2
def dataspaceName = 'my_dataspace'
def anchorName = 'my_anchor'
@@ -87,21 +89,25 @@ class DataRestControllerSpec extends Specification {
def expectedXmlData = '<?xml version=\'1.0\' encoding=\'UTF-8\'?>\n<bookstore xmlns="org:onap:ccsdk:sample">\n</bookstore>'
@Shared
- static DataNode dataNodeWithLeavesNoChildren = new DataNodeBuilder().withXpath('/xpath')
+ static DataNode dataNodeWithLeavesNoChildren = new DataNodeBuilder().withXpath('/parent-1')
.withLeaves([leaf: 'value', leafList: ['leaveListElement1', 'leaveListElement2']]).build()
@Shared
+ static DataNode dataNodeWithLeavesNoChildren2 = new DataNodeBuilder().withXpath('/parent-2')
+ .withLeaves([leaf: 'value']).build()
+
+ @Shared
static DataNode dataNodeWithChild = new DataNodeBuilder().withXpath('/parent')
.withChildDataNodes([new DataNodeBuilder().withXpath("/parent/child").build()]).build()
def setup() {
- dataNodeBaseEndpoint = "$basePath/v1/dataspaces/$dataspaceName"
+ dataNodeBaseEndpointV1 = "$basePath/v1/dataspaces/$dataspaceName"
dataNodeBaseEndpointV2 = "$basePath/v2/dataspaces/$dataspaceName"
}
def 'Create a node: #scenario.'() {
given: 'endpoint to create a node'
- def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
+ def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
when: 'post is invoked with datanode endpoint and json'
def response =
mvc.perform(
@@ -124,7 +130,7 @@ class DataRestControllerSpec extends Specification {
def 'Create a node with observed-timestamp'() {
given: 'endpoint to create a node'
- def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
+ def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
when: 'post is invoked with datanode endpoint and json'
def response =
mvc.perform(
@@ -148,7 +154,7 @@ class DataRestControllerSpec extends Specification {
def 'Create a child node #scenario'() {
given: 'endpoint to create a node'
- def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
+ def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
and: 'parent node xpath'
def parentNodeXpath = 'some xpath'
when: 'post is invoked with datanode endpoint and json'
@@ -177,7 +183,7 @@ class DataRestControllerSpec extends Specification {
given: 'parent node xpath '
def parentNodeXpath = 'parent node xpath'
when: 'list-node endpoint is invoked with post (create) operation'
- def postRequestBuilder = post("$dataNodeBaseEndpoint/anchors/$anchorName/list-nodes")
+ def postRequestBuilder = post("$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes")
.contentType(MediaType.APPLICATION_JSON)
.param('xpath', parentNodeXpath)
.content(requestBodyJson)
@@ -198,9 +204,9 @@ class DataRestControllerSpec extends Specification {
def 'Get data node with leaves'() {
given: 'the service returns data node leaves'
- def xpath = 'xpath'
- def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/node"
- mockCpsDataService.getDataNode(dataspaceName, anchorName, xpath, OMIT_DESCENDANTS) >> dataNodeWithLeavesNoChildren
+ def xpath = 'parent-1'
+ def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/node"
+ mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, OMIT_DESCENDANTS) >> [dataNodeWithLeavesNoChildren]
when: 'get request is performed through REST API'
def response =
mvc.perform(get(endpoint).param('xpath', xpath))
@@ -208,7 +214,7 @@ class DataRestControllerSpec extends Specification {
then: 'a success response is returned'
response.status == HttpStatus.OK.value()
then: 'the response contains the the datanode in json format'
- response.getContentAsString() == '{"xpath":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}}'
+ response.getContentAsString() == '{"parent-1":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}}'
and: 'response contains expected leaf and value'
response.contentAsString.contains('"leaf":"value"')
and: 'response contains expected leaf-list and values'
@@ -218,8 +224,8 @@ class DataRestControllerSpec extends Specification {
def 'Get data node with #scenario.'() {
given: 'the service returns data node with #scenario'
def xpath = 'some xPath'
- def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/node"
- mockCpsDataService.getDataNode(dataspaceName, anchorName, xpath, expectedCpsDataServiceOption) >> dataNode
+ def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/node"
+ mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, expectedCpsDataServiceOption) >> [dataNode]
when: 'get request is performed through REST API'
def response =
mvc.perform(
@@ -235,25 +241,68 @@ class DataRestControllerSpec extends Specification {
response.contentAsString.contains('"child"') == expectChildInResponse
where:
scenario | dataNode | includeDescendantsOption || expectedCpsDataServiceOption | expectChildInResponse | expectedRootidentifier
- 'no descendants by default' | dataNodeWithLeavesNoChildren | '' || OMIT_DESCENDANTS | false | 'xpath'
- 'no descendant explicitly' | dataNodeWithLeavesNoChildren | 'false' || OMIT_DESCENDANTS | false | 'xpath'
+ 'no descendants by default' | dataNodeWithLeavesNoChildren | '' || OMIT_DESCENDANTS | false | 'parent-1'
+ 'no descendant explicitly' | dataNodeWithLeavesNoChildren | 'false' || OMIT_DESCENDANTS | false | 'parent-1'
'with descendants' | dataNodeWithChild | 'true' || INCLUDE_ALL_DESCENDANTS | true | 'parent'
}
+ def 'Get all the data trees as json array with root node xPath using V2'() {
+ given: 'the service returns all data node leaves'
+ def xpath = '/'
+ def endpoint = "$dataNodeBaseEndpointV2/anchors/$anchorName/node"
+ mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, OMIT_DESCENDANTS) >> [dataNodeWithLeavesNoChildren, dataNodeWithLeavesNoChildren2]
+ when: 'V2 of get request is performed through REST API'
+ def response =
+ mvc.perform(get(endpoint).param('xpath', xpath))
+ .andReturn().response
+ then: 'a success response is returned'
+ response.status == HttpStatus.OK.value()
+ and: 'the response contains the datanode in json array format'
+ response.getContentAsString() == '[{"parent-1":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}},' +
+ '{"parent-2":{"leaf":"value"}}]'
+ and: 'the json array contains expected number of data trees'
+ def numberOfDataTrees = new JsonSlurper().parseText(response.getContentAsString()).iterator().size()
+ assert numberOfDataTrees == 2
+ }
+
+ def 'Get data node with #scenario using V2.'() {
+ given: 'the service returns data nodes with #scenario'
+ def xpath = 'some xPath'
+ def endpoint = "$dataNodeBaseEndpointV2/anchors/$anchorName/node"
+ mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, expectedCpsDataServiceOption) >> [dataNode]
+ when: 'V2 of get request is performed through REST API'
+ def response =
+ mvc.perform(
+ get(endpoint)
+ .param('xpath', xpath)
+ .param('descendants', includeDescendantsOption))
+ .andReturn().response
+ then: 'a success response is returned'
+ response.status == HttpStatus.OK.value()
+ and: 'the response contains the root node identifier: #expectedRootidentifier'
+ response.contentAsString.contains(expectedRootidentifier)
+ and: 'the response contains child is #expectChildInResponse'
+ response.contentAsString.contains('"child"') == expectChildInResponse
+ where:
+ scenario | dataNode | includeDescendantsOption || expectedCpsDataServiceOption | expectChildInResponse | expectedRootidentifier
+ 'no descendants by default' | dataNodeWithLeavesNoChildren | '' || OMIT_DESCENDANTS | false | 'parent-1'
+ 'no descendant explicitly' | dataNodeWithLeavesNoChildren | '0' || OMIT_DESCENDANTS | false | 'parent-1'
+ 'with descendants' | dataNodeWithChild | '-1' || INCLUDE_ALL_DESCENDANTS | true | 'parent'
+ }
def 'Get data node using v2 api'() {
given: 'the service returns data node'
def xpath = 'some xPath'
def endpoint = "$dataNodeBaseEndpointV2/anchors/$anchorName/node"
- mockCpsDataService.getDataNode(dataspaceName, anchorName, xpath, {descendantsOption -> {
- assert descendantsOption.depth == 2}}) >> dataNodeWithChild
+ mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, { descendantsOption -> {
+ assert descendantsOption.depth == 2}} as FetchDescendantsOption) >> [dataNodeWithChild]
when: 'get request is performed through REST API'
def response =
mvc.perform(
- get(endpoint)
- .param('xpath', xpath)
- .param('descendants', '2'))
- .andReturn().response
+ get(endpoint)
+ .param('xpath', xpath)
+ .param('descendants', '2'))
+ .andReturn().response
then: 'a success response is returned'
assert response.status == HttpStatus.OK.value()
and: 'the response contains the root node identifier'
@@ -264,7 +313,7 @@ class DataRestControllerSpec extends Specification {
def 'Update data node leaves: #scenario.'() {
given: 'endpoint to update a node '
- def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
+ def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
when: 'patch request is performed'
def response =
mvc.perform(
@@ -286,7 +335,7 @@ class DataRestControllerSpec extends Specification {
def 'Update data node leaves with observedTimestamp'() {
given: 'endpoint to update a node leaves '
- def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
+ def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
when: 'patch request is performed'
def response =
mvc.perform(
@@ -309,7 +358,7 @@ class DataRestControllerSpec extends Specification {
def 'Replace data node tree: #scenario.'() {
given: 'endpoint to replace node'
- def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
+ def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
when: 'put request is performed'
def response =
mvc.perform(
@@ -331,7 +380,7 @@ class DataRestControllerSpec extends Specification {
def 'Update data node and descendants with observedTimestamp.'() {
given: 'endpoint to replace node'
- def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
+ def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
when: 'put request is performed'
def response =
mvc.perform(
@@ -354,7 +403,7 @@ class DataRestControllerSpec extends Specification {
def 'Replace list content #scenario.'() {
when: 'list-nodes endpoint is invoked with put (update) operation'
- def putRequestBuilder = put("$dataNodeBaseEndpoint/anchors/$anchorName/list-nodes")
+ def putRequestBuilder = put("$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes")
.contentType(MediaType.APPLICATION_JSON)
.param('xpath', 'parent xpath')
.content(requestBodyJson)
@@ -375,7 +424,7 @@ class DataRestControllerSpec extends Specification {
def 'Delete list element #scenario.'() {
when: 'list-nodes endpoint is invoked with delete operation'
- def deleteRequestBuilder = delete("$dataNodeBaseEndpoint/anchors/$anchorName/list-nodes")
+ def deleteRequestBuilder = delete("$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes")
.param('xpath', 'list element xpath')
if (observedTimestamp != null)
deleteRequestBuilder.param('observed-timestamp', observedTimestamp)
@@ -396,7 +445,7 @@ class DataRestControllerSpec extends Specification {
given: 'data node xpath'
def dataNodeXpath = '/dataNodeXpath'
when: 'delete data node endpoint is invoked'
- def deleteDataNodeRequest = delete( "$dataNodeBaseEndpoint/anchors/$anchorName/nodes")
+ def deleteDataNodeRequest = delete( "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes")
.param('xpath', dataNodeXpath)
and: 'observed timestamp is added to the parameters'
if (observedTimestamp != null)
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java
index 5b310efd5d..46439fdb37 100644
--- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java
@@ -3,7 +3,7 @@
* Copyright (C) 2021-2023 Nordix Foundation
* Modifications Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2020-2022 Bell Canada.
- * Modifications Copyright (C) 2022 TechMahindra Ltd.
+ * Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -249,17 +249,22 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
}
@Override
- public DataNode getDataNode(final String dataspaceName, final String anchorName, final String xpath,
- final FetchDescendantsOption fetchDescendantsOption) {
- final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath,
- fetchDescendantsOption);
- return toDataNode(fragmentEntity, fetchDescendantsOption);
+ public Collection<DataNode> getDataNodes(final String dataspaceName, final String anchorName,
+ final String xpath,
+ final FetchDescendantsOption fetchDescendantsOption) {
+ final String targetXpath = isRootXpath(xpath) ? xpath : CpsPathUtil.getNormalizedXpath(xpath);
+ final Collection<DataNode> dataNodes = getDataNodesForMultipleXpaths(dataspaceName, anchorName,
+ Collections.singletonList(targetXpath), fetchDescendantsOption);
+ if (dataNodes.isEmpty()) {
+ throw new DataNodeNotFoundException(dataspaceName, anchorName, xpath);
+ }
+ return dataNodes;
}
@Override
- public Collection<DataNode> getDataNodes(final String dataspaceName, final String anchorName,
- final Collection<String> xpaths,
- final FetchDescendantsOption fetchDescendantsOption) {
+ public Collection<DataNode> getDataNodesForMultipleXpaths(final String dataspaceName, final String anchorName,
+ final Collection<String> xpaths,
+ final FetchDescendantsOption fetchDescendantsOption) {
final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
@@ -271,7 +276,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
try {
normalizedXpaths.add(CpsPathUtil.getNormalizedXpath(xpath));
} catch (final PathParsingException e) {
- log.warn("Error parsing xpath \"{}\" in getDataNodes: {}", xpath, e.getMessage());
+ log.warn("Error parsing xpath \"{}\" in getDataNodesForMultipleXpaths: {}", xpath, e.getMessage());
}
}
final Collection<FragmentEntity> fragmentEntities =
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy
index e4c552978d..336d6035ae 100755
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy
@@ -3,7 +3,7 @@
* Copyright (C) 2021-2023 Nordix Foundation
* Modifications Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2021-2022 Bell Canada.
- * Modifications Copyright (C) 2022 TechMahindra Ltd.
+ * Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,6 +41,7 @@ import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.context.jdbc.Sql
import javax.validation.ConstraintViolationException
+import java.nio.file.Path
import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
@@ -82,15 +83,13 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
]
@Sql([CLEAR_DATA, SET_DATA])
- def 'Get existing datanode with descendants.'() {
- when: 'the node is retrieved by its xpath'
- def dataNode = objectUnderTest.getDataNode(DATASPACE_NAME, ANCHOR_NAME1, '/parent-1', INCLUDE_ALL_DESCENDANTS)
- then: 'the path and prefix are populated correctly'
- assert dataNode.xpath == '/parent-1'
- and: 'dataNode has no prefix (to be addressed by CPS-1301'
- assert dataNode.moduleNamePrefix == null
- and: 'the child node has the correct path'
- assert dataNode.childDataNodes[0].xpath == '/parent-1/child-1'
+ def 'Get all datanodes with descendants .'() {
+ when: 'data nodes are retrieved by their xpath'
+ def dataNodes = objectUnderTest.getDataNodesForMultipleXpaths(DATASPACE_NAME, ANCHOR_NAME1, ['/parent-1'], INCLUDE_ALL_DESCENDANTS)
+ then: 'same data nodes are returned by getDataNodesForMultipleXpaths method'
+ assert objectUnderTest.getDataNodes(DATASPACE_NAME, ANCHOR_NAME1, '/parent-1', INCLUDE_ALL_DESCENDANTS) == dataNodes
+ and: 'the dataNodes have no prefix (to be addressed by CPS-1301)'
+ assert dataNodes[0].moduleNamePrefix == null
}
@Sql([CLEAR_DATA, SET_DATA])
@@ -102,11 +101,11 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
def dataNodes = [createDataNodeTree(parentXpath, childXpath, grandChildXpath)]
objectUnderTest.storeDataNodes(DATASPACE_NAME, ANCHOR_NAME1, dataNodes)
then: 'it can be retrieved by its xpath'
- def dataNode = objectUnderTest.getDataNode(DATASPACE_NAME, ANCHOR_NAME1, parentXpath, INCLUDE_ALL_DESCENDANTS)
- assert dataNode.xpath == parentXpath
+ def dataNode = objectUnderTest.getDataNodes(DATASPACE_NAME, ANCHOR_NAME1, parentXpath, INCLUDE_ALL_DESCENDANTS)
+ assert dataNode[0].xpath == parentXpath
and: 'it has the correct child'
- assert dataNode.childDataNodes.size() == 1
- def childDataNode = dataNode.childDataNodes[0]
+ assert dataNode[0].childDataNodes.size() == 1
+ def childDataNode = dataNode[0].childDataNodes[0]
assert childDataNode.xpath == childXpath
and: 'and its grandchild'
assert childDataNode.childDataNodes.size() == 1
@@ -236,18 +235,19 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
}
@Sql([CLEAR_DATA, SET_DATA])
- def 'Get data node by xpath without descendants.'() {
- when: 'data node is requested'
- def result = objectUnderTest.getDataNode(DATASPACE_NAME, ANCHOR_HAVING_SINGLE_TOP_LEVEL_FRAGMENT,
- inputXPath, OMIT_DESCENDANTS)
- then: 'data node is returned with no descendants'
- assert result.xpath == XPATH_DATA_NODE_WITH_LEAVES
- and: 'expected leaves'
- assert result.childDataNodes.size() == 0
- assertLeavesMaps(result.leaves, expectedLeavesByXpathMap[XPATH_DATA_NODE_WITH_LEAVES])
- where: 'the following data is used'
+ def 'Get all data nodes by single xpath without descendants : #scenario'() {
+ when: 'data nodes are requested'
+ def result = objectUnderTest.getDataNodesForMultipleXpaths(DATASPACE_NAME, ANCHOR_WITH_MULTIPLE_TOP_LEVEL_FRAGMENTS,
+ [inputXPath], OMIT_DESCENDANTS)
+ then: 'data nodes under root are returned'
+ assert result.childDataNodes.size() == 2
+ and: 'no descendants of parent nodes are returned'
+ result.each {assert it.childDataNodes.size() == 0}
+ and: 'same data nodes are returned when V2 of get Data Nodes API is executed'
+ assert objectUnderTest.getDataNodes(DATASPACE_NAME, ANCHOR_WITH_MULTIPLE_TOP_LEVEL_FRAGMENTS,
+ inputXPath, OMIT_DESCENDANTS) == result
+ where: 'the following xpath is used'
scenario | inputXPath
- 'some xpath' | '/parent-207'
'root xpath' | '/'
'empty xpath' | ''
}
@@ -255,51 +255,50 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
@Sql([CLEAR_DATA, SET_DATA])
def 'Cps Path query with syntax error throws a CPS Path Exception.'() {
when: 'trying to execute a query with a syntax (parsing) error'
- objectUnderTest.getDataNode(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, 'invalid-cps-path/child' , OMIT_DESCENDANTS)
+ objectUnderTest.getDataNodes(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, 'invalid-cps-path/child' , OMIT_DESCENDANTS)
then: 'exception is thrown'
- def exceptionThrown = thrown(CpsPathException)
- assert exceptionThrown.getDetails().contains('failed to parse at line 1 due to extraneous input \'invalid-cps-path\' expecting \'/\'')
+ def exceptionThrown = thrown(PathParsingException)
+ assert exceptionThrown.getMessage().contains('failed to parse at line 1 due to extraneous input \'invalid-cps-path\' expecting \'/\'')
}
@Sql([CLEAR_DATA, SET_DATA])
- def 'Get data node by xpath with all descendants.'() {
- when: 'data node is requested with all descendants'
- def result = objectUnderTest.getDataNode(DATASPACE_NAME, ANCHOR_HAVING_SINGLE_TOP_LEVEL_FRAGMENT,
- inputXPath, INCLUDE_ALL_DESCENDANTS)
- def mappedResult = treeToFlatMapByXpath(new HashMap<>(), result)
- then: 'data node is returned with all the descendants populated'
- assert mappedResult.size() == 4
+ def 'Get all data nodes by single xpath with all descendants : #scenario'() {
+ when: 'data nodes are requested with all descendants'
+ def result = objectUnderTest.getDataNodesForMultipleXpaths(DATASPACE_NAME, ANCHOR_WITH_MULTIPLE_TOP_LEVEL_FRAGMENTS,
+ [inputXPath], INCLUDE_ALL_DESCENDANTS)
+ def mappedResult = multipleTreesToFlatMapByXpath(new HashMap<>(), result)
+ then: 'data nodes are returned with all the descendants populated'
+ assert mappedResult.size() == 8
assert result.childDataNodes.size() == 2
- assert mappedResult.get('/parent-207/child-001').childDataNodes.size() == 0
- assert mappedResult.get('/parent-207/child-002').childDataNodes.size() == 1
- and: 'extracted leaves maps are matching expected'
- mappedResult.forEach(
- (xPath, dataNode) -> assertLeavesMaps(dataNode.leaves, expectedLeavesByXpathMap[xPath]))
+ assert mappedResult.get('/parent-208/child-001').childDataNodes.size() == 0
+ assert mappedResult.get('/parent-208/child-002').childDataNodes.size() == 1
+ assert mappedResult.get('/parent-209/child-001').childDataNodes.size() == 0
+ assert mappedResult.get('/parent-209/child-002').childDataNodes.size() == 1
+ and: 'same data nodes are returned when V2 of Get Data Nodes API is executed'
+ assert objectUnderTest.getDataNodes(DATASPACE_NAME, ANCHOR_WITH_MULTIPLE_TOP_LEVEL_FRAGMENTS,
+ inputXPath, INCLUDE_ALL_DESCENDANTS) == result
where: 'the following data is used'
scenario | inputXPath
- 'some xpath' | '/parent-207'
'root xpath' | '/'
'empty xpath' | ''
}
@Sql([CLEAR_DATA, SET_DATA])
- def 'Get data node error scenario: #scenario.'() {
- when: 'attempt to get data node with #scenario'
- objectUnderTest.getDataNode(dataspaceName, anchorName, xpath, OMIT_DESCENDANTS)
- then: 'a #expectedException is thrown'
+ def 'Get data nodes error scenario : #scenario.'() {
+ when: 'attempt to get data nodes with #scenario'
+ objectUnderTest.getDataNodes(dataspaceName, anchorName, xpath, OMIT_DESCENDANTS)
+ then: 'an #expectedException is thrown'
thrown(expectedException)
where: 'the following data is used'
- scenario | dataspaceName | anchorName | xpath || expectedException
- 'non-existing dataspace' | 'NO DATASPACE' | 'not relevant' | '/not relevant' || DataspaceNotFoundException
- 'non-existing anchor' | DATASPACE_NAME | 'NO ANCHOR' | '/not relevant' || AnchorNotFoundException
- 'non-existing xpath' | DATASPACE_NAME | ANCHOR_FOR_DATA_NODES_WITH_LEAVES | '/NO-XPATH' || DataNodeNotFoundException
- 'invalid xpath' | DATASPACE_NAME | ANCHOR_FOR_DATA_NODES_WITH_LEAVES | 'INVALID XPATH' || CpsPathException
+ scenario | dataspaceName | anchorName | xpath || expectedException
+ 'non existing xpath' | DATASPACE_NAME | ANCHOR_FOR_DATA_NODES_WITH_LEAVES | '/NO-XPATH' || DataNodeNotFoundException
+ 'invalid Xpath' | DATASPACE_NAME | ANCHOR_FOR_DATA_NODES_WITH_LEAVES | 'INVALID XPATH' || PathParsingException
}
@Sql([CLEAR_DATA, SET_DATA])
- def 'Get multiple data nodes by xpath.'() {
+ def 'Get data nodes for multiple xpaths.'() {
when: 'fetch #scenario.'
- def results = objectUnderTest.getDataNodes(DATASPACE_NAME, ANCHOR_NAME3, inputXpaths, OMIT_DESCENDANTS)
+ def results = objectUnderTest.getDataNodesForMultipleXpaths(DATASPACE_NAME, ANCHOR_NAME3, inputXpaths, OMIT_DESCENDANTS)
then: 'the expected number of data nodes are returned'
assert results.size() == expectedResultSize
where: 'following parameters were used'
@@ -323,9 +322,9 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
}
@Sql([CLEAR_DATA, SET_DATA])
- def 'Get multiple data nodes error scenario: #scenario.'() {
+ def 'Get data nodes for collection of xpath error scenario : #scenario.'() {
when: 'attempt to get data nodes with #scenario'
- objectUnderTest.getDataNodes(dataspaceName, anchorName, ['/not-relevant'], OMIT_DESCENDANTS)
+ objectUnderTest.getDataNodesForMultipleXpaths(dataspaceName, anchorName, ['/not-relevant'], OMIT_DESCENDANTS)
then: 'a #expectedException is thrown'
thrown(expectedException)
where: 'the following data is used'
@@ -599,11 +598,11 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
def child = new DataNodeBuilder().withXpath(deleteTestChildXpath).withChildDataNodes([grandChild]).build()
objectUnderTest.addChildDataNode(DATASPACE_NAME, ANCHOR_NAME3, deleteTestParentXPath, child)
and: 'number of children before delete is stored'
- def numberOfChildrenBeforeDelete = objectUnderTest.getDataNode(DATASPACE_NAME, ANCHOR_NAME3, pathToParentOfDeletedNode, INCLUDE_ALL_DESCENDANTS).childDataNodes.size()
+ def numberOfChildrenBeforeDelete = objectUnderTest.getDataNodes(DATASPACE_NAME, ANCHOR_NAME3, pathToParentOfDeletedNode, INCLUDE_ALL_DESCENDANTS)[0].childDataNodes.size()
when: 'target node is deleted'
objectUnderTest.deleteDataNode(DATASPACE_NAME, ANCHOR_NAME3, deleteTarget)
then: 'one child has been deleted'
- def numberOfChildrenAfterDelete = objectUnderTest.getDataNode(DATASPACE_NAME, ANCHOR_NAME3, pathToParentOfDeletedNode, INCLUDE_ALL_DESCENDANTS).childDataNodes.size()
+ def numberOfChildrenAfterDelete = objectUnderTest.getDataNodes(DATASPACE_NAME, ANCHOR_NAME3, pathToParentOfDeletedNode, INCLUDE_ALL_DESCENDANTS)[0].childDataNodes.size()
assert numberOfChildrenAfterDelete == numberOfChildrenBeforeDelete - 1
where:
scenario | deleteTarget | pathToParentOfDeletedNode
@@ -634,13 +633,13 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
and: 'data nodes are deleted'
objectUnderTest.deleteDataNode(DATASPACE_NAME, ANCHOR_NAME3, xpathForDeletion)
when: 'verify data nodes are removed'
- objectUnderTest.getDataNode(DATASPACE_NAME, ANCHOR_NAME3, xpathForDeletion, INCLUDE_ALL_DESCENDANTS)
+ objectUnderTest.getDataNodes(DATASPACE_NAME, ANCHOR_NAME3, xpathForDeletion, INCLUDE_ALL_DESCENDANTS)
then:
thrown(DataNodeNotFoundException)
and: 'some related object is not deleted'
if (xpathSurvivor!=null) {
- dataNode = objectUnderTest.getDataNode(DATASPACE_NAME, ANCHOR_NAME3, xpathSurvivor, INCLUDE_ALL_DESCENDANTS)
- assert dataNode.xpath == xpathSurvivor
+ dataNode = objectUnderTest.getDataNodes(DATASPACE_NAME, ANCHOR_NAME3, xpathSurvivor, INCLUDE_ALL_DESCENDANTS)
+ assert dataNode[0].xpath == xpathSurvivor
}
where: 'following parameters were used'
scenario | xpathForDeletion || xpathSurvivor
@@ -711,6 +710,15 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
return flatMap
}
+ def static multipleTreesToFlatMapByXpath(Map<String, DataNode> flatMap, Collection<DataNode> dataNodeTrees) {
+ for (DataNode dataNodeTree: dataNodeTrees){
+ flatMap.put(dataNodeTree.xpath, dataNodeTree)
+ dataNodeTree.childDataNodes
+ .forEach(childDataNode -> multipleTreesToFlatMapByXpath(flatMap, [childDataNode]))
+ }
+ return flatMap
+ }
+
def keysToXpaths(parent, Collection keys) {
return keys.collect { "${parent}/child-list[@key='${it}']".toString() }
}
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy
index 5cabc85b36..ac66e8c023 100644
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy
@@ -25,7 +25,6 @@ import org.hibernate.StaleStateException
import org.onap.cps.spi.FetchDescendantsOption
import org.onap.cps.spi.entities.AnchorEntity
import org.onap.cps.spi.entities.FragmentEntity
-import org.onap.cps.spi.entities.FragmentExtract
import org.onap.cps.spi.exceptions.ConcurrencyException
import org.onap.cps.spi.exceptions.DataValidationException
import org.onap.cps.spi.model.DataNode
@@ -112,10 +111,10 @@ class CpsDataPersistenceServiceSpec extends Specification {
given: 'the db has a fragment with an attribute property JSON value of #scenario'
mockFragmentWithJson("{\"some attribute\": ${dataString}}")
when: 'getting the data node represented by this fragment'
- def dataNode = objectUnderTest.getDataNode('my-dataspace', 'my-anchor',
+ def dataNode = objectUnderTest.getDataNodes('my-dataspace', 'my-anchor',
'/parent-01', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
then: 'the leaf is of the correct value and data type'
- def attributeValue = dataNode.leaves.get('some attribute')
+ def attributeValue = dataNode[0].leaves.get('some attribute')
assert attributeValue == expectedValue
assert attributeValue.class == expectedDataClass
where: 'the following Data Type is passed'
@@ -136,7 +135,7 @@ class CpsDataPersistenceServiceSpec extends Specification {
given: 'a fragment with invalid JSON'
mockFragmentWithJson('{invalid json')
when: 'getting the data node represented by this fragment'
- objectUnderTest.getDataNode('my-dataspace', 'my-anchor',
+ objectUnderTest.getDataNodes('my-dataspace', 'my-anchor',
'/parent-01', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
then: 'a data validation exception is thrown'
thrown(DataValidationException)
@@ -151,7 +150,7 @@ class CpsDataPersistenceServiceSpec extends Specification {
def fragmentEntity2 = new FragmentEntity(xpath: '/xpath2', childFragments: [])
mockFragmentRepository.findByAnchorAndMultipleCpsPaths(123, ['/xpath1', '/xpath2'] as Set<String>) >> [fragmentEntity1, fragmentEntity2]
when: 'getting data nodes for 2 xpaths'
- def result = objectUnderTest.getDataNodes('some-dataspace', 'some-anchor', ['/xpath1', '/xpath2'], FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
+ def result = objectUnderTest.getDataNodesForMultipleXpaths('some-dataspace', 'some-anchor', ['/xpath1', '/xpath2'], FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
then: '2 data nodes are returned'
assert result.size() == 2
}
@@ -243,10 +242,8 @@ class CpsDataPersistenceServiceSpec extends Specification {
def mockFragmentWithJson(json) {
def anchorEntity = new AnchorEntity(id:123)
mockAnchorRepository.getByDataspaceAndName(*_) >> anchorEntity
- def mockFragmentExtract = Mock(FragmentExtract)
- mockFragmentExtract.getId() >> 456
- mockFragmentExtract.getAttributes() >> json
- mockFragmentRepository.findByAnchorIdAndParentXpath(*_) >> [mockFragmentExtract]
+ def fragmentEntity = new FragmentEntity(xpath: '/parent-01', childFragments: [], attributes: json)
+ mockFragmentRepository.findByAnchorAndMultipleCpsPaths(123, ['/parent-01'] as Set<String>) >> [fragmentEntity]
}
}
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistenceSpecBase.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistenceSpecBase.groovy
index 1ecad4e68c..30ff11b458 100644
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistenceSpecBase.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistenceSpecBase.groovy
@@ -3,6 +3,7 @@
* Copyright (C) 2021-2022 Nordix Foundation
* Modifications Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2021 Bell Canada.
+ * Modifications Copyright (C) 2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
@@ -60,13 +61,14 @@ class CpsPersistenceSpecBase extends Specification {
static final String CLEAR_DATA = '/data/clear-all.sql'
- static final String DATASPACE_NAME = 'DATASPACE-001'
- static final String SCHEMA_SET_NAME1 = 'SCHEMA-SET-001'
- static final String SCHEMA_SET_NAME2 = 'SCHEMA-SET-002'
- static final String ANCHOR_NAME1 = 'ANCHOR-001'
- static final String ANCHOR_NAME2 = 'ANCHOR-002'
- static final String ANCHOR_NAME3 = 'ANCHOR-003'
- static final String ANCHOR_FOR_DATA_NODES_WITH_LEAVES = 'ANCHOR-003'
- static final String ANCHOR_FOR_SHOP_EXAMPLE = 'ANCHOR-004'
- static final String ANCHOR_HAVING_SINGLE_TOP_LEVEL_FRAGMENT = 'ANCHOR-005'
+ static def DATASPACE_NAME = 'DATASPACE-001'
+ static def SCHEMA_SET_NAME1 = 'SCHEMA-SET-001'
+ static def SCHEMA_SET_NAME2 = 'SCHEMA-SET-002'
+ static def ANCHOR_NAME1 = 'ANCHOR-001'
+ static def ANCHOR_NAME2 = 'ANCHOR-002'
+ static def ANCHOR_NAME3 = 'ANCHOR-003'
+ static def ANCHOR_FOR_DATA_NODES_WITH_LEAVES = 'ANCHOR-003'
+ static def ANCHOR_FOR_SHOP_EXAMPLE = 'ANCHOR-004'
+ static def ANCHOR_HAVING_SINGLE_TOP_LEVEL_FRAGMENT = 'ANCHOR-005'
+ static def ANCHOR_WITH_MULTIPLE_TOP_LEVEL_FRAGMENTS = 'ANCHOR-006'
}
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServicePerfTest.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServicePerfTest.groovy
index 0c4f5ec41e..246fd80bba 100644
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServicePerfTest.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServicePerfTest.groovy
@@ -64,13 +64,13 @@ class CpsDataPersistenceServicePerfTest extends CpsPersistencePerfSpecBase {
def 'Get data node with many descendants by xpath #scenario'() {
when: 'get parent is executed with all descendants'
stopWatch.start()
- def result = objectUnderTest.getDataNode(PERF_DATASPACE, PERF_ANCHOR, xpath, INCLUDE_ALL_DESCENDANTS)
+ def result = objectUnderTest.getDataNodes(PERF_DATASPACE, PERF_ANCHOR, xpath, INCLUDE_ALL_DESCENDANTS)
stopWatch.stop()
def readDurationInMillis = stopWatch.getTotalTimeMillis()
then: 'read duration is under 500 milliseconds'
recordAndAssertPerformance("Get ${scenario}", 500, readDurationInMillis)
and: 'data node is returned with all the descendants populated'
- assert countDataNodes(result) == TOTAL_NUMBER_OF_NODES
+ assert countDataNodes(result[0]) == TOTAL_NUMBER_OF_NODES
where: 'the following xPaths are used'
scenario || xpath
'parent' || PERF_TEST_PARENT
@@ -93,7 +93,7 @@ class CpsDataPersistenceServicePerfTest extends CpsPersistencePerfSpecBase {
when: 'we query for all grandchildren (except 1 for fun) with the new native method'
xpathsToAllGrandChildren.remove(0)
stopWatch.start()
- def result = objectUnderTest.getDataNodes(PERF_DATASPACE, PERF_ANCHOR, xpathsToAllGrandChildren, INCLUDE_ALL_DESCENDANTS)
+ def result = objectUnderTest.getDataNodesForMultipleXpaths(PERF_DATASPACE, PERF_ANCHOR, xpathsToAllGrandChildren, INCLUDE_ALL_DESCENDANTS)
stopWatch.stop()
def readDurationInMillis = stopWatch.getTotalTimeMillis()
then: 'the returned number of entities equal to the number of children * number of grandchildren'
diff --git a/cps-ri/src/test/resources/data/fragment.sql b/cps-ri/src/test/resources/data/fragment.sql
index ad463cffd5..caafcd320a 100755
--- a/cps-ri/src/test/resources/data/fragment.sql
+++ b/cps-ri/src/test/resources/data/fragment.sql
@@ -52,7 +52,8 @@ INSERT INTO ANCHOR (ID, NAME, DATASPACE_ID, SCHEMA_SET_ID) VALUES
(3001, 'ANCHOR-001', 1001, 2001),
(3003, 'ANCHOR-003', 1001, 2001),
(3004, 'ncmp-dmi-registry', 1002, 2001),
- (3005, 'ANCHOR-005', 1001, 2001);
+ (3005, 'ANCHOR-005', 1001, 2001),
+ (3006, 'ANCHOR-006', 1001, 2001);
INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH) VALUES
(4001, 1001, 3001, null, '/parent-1'),
@@ -69,6 +70,16 @@ INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH, ATTRIBUTES)
(5012, 1001, 3005, 5011, '/parent-207/child-002/grand-child', '{"grand-child-leaf": "grand-child-leaf value"}');
INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH, ATTRIBUTES) VALUES
+ (5013, 1001, 3006, null, '/parent-208', '{"parent-leaf-1": "parent-leaf value-1"}'),
+ (5014, 1001, 3006, 5013, '/parent-208/child-001', '{"first-child-leaf": "first-child-leaf value"}'),
+ (5015, 1001, 3006, 5013, '/parent-208/child-002', '{"second-child-leaf": "second-child-leaf value"}'),
+ (5016, 1001, 3006, 5015, '/parent-208/child-002/grand-child', '{"grand-child-leaf": "grand-child-leaf value"}'),
+ (5017, 1001, 3006, null, '/parent-209', '{"parent-leaf-2": "parent-leaf value-2"}'),
+ (5018, 1001, 3006, 5017, '/parent-209/child-001', '{"first-child-leaf": "first-child-leaf value"}'),
+ (5019, 1001, 3006, 5017, '/parent-209/child-002', '{"second-child-leaf": "second-child-leaf value"}'),
+ (5020, 1001, 3006, 5019, '/parent-209/child-002/grand-child', '{"grand-child-leaf": "grand-child-leaf value"}');
+
+INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH, ATTRIBUTES) VALUES
(4201, 1001, 3003, null, '/parent-200', '{"leaf-value": "original"}'),
(4202, 1001, 3003, 4201, '/parent-200/child-201', '{"leaf-value": "original"}'),
(4203, 1001, 3003, 4202, '/parent-200/child-201/grand-child', '{"leaf-value": "original"}'),
diff --git a/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java b/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java
index 174d71f64d..9f96df2e4d 100644
--- a/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java
+++ b/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java
@@ -4,6 +4,7 @@
* Modifications Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2021-2022 Bell Canada
* Modifications Copyright (C) 2022 Deutsche Telekom AG
+ * Modifications Copyright (C) 2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -110,30 +111,31 @@ public interface CpsDataService {
Collection<String> jsonDataList, OffsetDateTime observedTimestamp);
/**
- * Retrieves datanode by XPath for given dataspace and anchor.
+ * Retrieves all the datanodes by XPath for given dataspace and anchor.
*
- * @param dataspaceName dataspace name
- * @param anchorName anchor name
- * @param xpath xpath
- * @param fetchDescendantsOption defines the scope of data to fetch: either single node or all the descendant nodes
- * (recursively) as well
- * @return data node object
+ * @param dataspaceName dataspace name
+ * @param anchorName anchor name
+ * @param xpath xpath
+ * @param fetchDescendantsOption defines the scope of data to fetch: either single node or all the descendant nodes
+ * (recursively) as well
+ * @return collection of data node objects
*/
- DataNode getDataNode(String dataspaceName, String anchorName, String xpath,
- FetchDescendantsOption fetchDescendantsOption);
+ Collection<DataNode> getDataNodes(String dataspaceName, String anchorName, String xpath,
+ FetchDescendantsOption fetchDescendantsOption);
/**
- * Retrieves datanodes by XPath for given dataspace and anchor.
+ * Retrieves all the datanodes for multiple XPaths for given dataspace and anchor.
*
- * @param dataspaceName dataspace name
- * @param anchorName anchor name
- * @param xpaths collection of xpath
- * @param fetchDescendantsOption defines the scope of data to fetch: either single node or all the descendant nodes
- * (recursively) as well
- * @return data node object
+ * @param dataspaceName dataspace name
+ * @param anchorName anchor name
+ * @param xpaths collection of xpaths
+ * @param fetchDescendantsOption defines the scope of data to fetch: either single node or all the descendant nodes
+ * (recursively) as well
+ * @return collection of data node objects
*/
- Collection<DataNode> getDataNodes(String dataspaceName, String anchorName, Collection<String> xpaths,
- FetchDescendantsOption fetchDescendantsOption);
+ Collection<DataNode> getDataNodesForMultipleXpaths(String dataspaceName, String anchorName,
+ Collection<String> xpaths,
+ FetchDescendantsOption fetchDescendantsOption);
/**
* Updates data node for given dataspace and anchor using xpath to parent node.
diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
index 06a0845385..b149d426e1 100755
--- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
+++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
@@ -3,7 +3,7 @@
* Copyright (C) 2021-2023 Nordix Foundation
* Modifications Copyright (C) 2020-2022 Bell Canada.
* Modifications Copyright (C) 2021 Pantheon.tech
- * Modifications Copyright (C) 2022 TechMahindra Ltd.
+ * Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
* Modifications Copyright (C) 2022 Deutsche Telekom AG
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -134,21 +134,23 @@ public class CpsDataServiceImpl implements CpsDataService {
@Override
@Timed(value = "cps.data.service.datanode.get",
- description = "Time taken to get a data node")
- public DataNode getDataNode(final String dataspaceName, final String anchorName, final String xpath,
- final FetchDescendantsOption fetchDescendantsOption) {
+ 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) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
- return cpsDataPersistenceService.getDataNode(dataspaceName, anchorName, xpath, fetchDescendantsOption);
+ return cpsDataPersistenceService.getDataNodes(dataspaceName, anchorName, xpath, fetchDescendantsOption);
}
@Override
@Timed(value = "cps.data.service.datanode.batch.get",
description = "Time taken to get a batch of data nodes")
- public Collection<DataNode> getDataNodes(final String dataspaceName, final String anchorName,
- final Collection<String> xpaths,
- final FetchDescendantsOption fetchDescendantsOption) {
+ public Collection<DataNode> getDataNodesForMultipleXpaths(final String dataspaceName, final String anchorName,
+ final Collection<String> xpaths,
+ final FetchDescendantsOption fetchDescendantsOption) {
cpsValidator.validateNameCharacters(dataspaceName, anchorName);
- return cpsDataPersistenceService.getDataNodes(dataspaceName, anchorName, xpaths, fetchDescendantsOption);
+ return cpsDataPersistenceService.getDataNodesForMultipleXpaths(dataspaceName, anchorName, xpaths,
+ fetchDescendantsOption);
}
@Override
diff --git a/cps-service/src/main/java/org/onap/cps/notification/CpsDataUpdatedEventFactory.java b/cps-service/src/main/java/org/onap/cps/notification/CpsDataUpdatedEventFactory.java
index f0cdaee8d6..38f8988279 100644
--- a/cps-service/src/main/java/org/onap/cps/notification/CpsDataUpdatedEventFactory.java
+++ b/cps-service/src/main/java/org/onap/cps/notification/CpsDataUpdatedEventFactory.java
@@ -2,6 +2,7 @@
* ============LICENSE_START=======================================================
* Copyright (c) 2021-2022 Bell Canada.
* Modifications Copyright (c) 2022 Nordix Foundation
+ * Modifications Copyright (C) 2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -76,8 +77,8 @@ public class CpsDataUpdatedEventFactory {
public CpsDataUpdatedEvent createCpsDataUpdatedEvent(final Anchor anchor,
final OffsetDateTime observedTimestamp, final Operation operation) {
final var dataNode = (operation == Operation.DELETE) ? null :
- cpsDataService.getDataNode(anchor.getDataspaceName(), anchor.getName(),
- "/", FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS);
+ cpsDataService.getDataNodes(anchor.getDataspaceName(), anchor.getName(),
+ "/", FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS).iterator().next();
return toCpsDataUpdatedEvent(anchor, dataNode, observedTimestamp, operation);
}
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 3e0b4475e8..0b2cef9bd9 100644
--- a/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java
+++ b/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java
@@ -3,7 +3,7 @@
* Copyright (C) 2020-2023 Nordix Foundation.
* Modifications Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2022 Bell Canada
- * Modifications Copyright (C) 2022 TechMahindra Ltd.
+ * Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -99,30 +99,33 @@ public interface CpsDataPersistenceService {
Collection<Collection<DataNode>> newLists);
/**
- * Retrieves datanode by XPath for given dataspace and anchor.
+ * Retrieves multiple datanodes for a single XPath for given dataspace and anchor.
+ * Multiple data nodes are returned when xPath is set to root '/', otherwise single data node
+ * is returned when a specific xpath is used (Example: /bookstore).
*
* @param dataspaceName dataspace name
* @param anchorName anchor name
- * @param xpath xpath
+ * @param xpath one xpath
* @param fetchDescendantsOption defines the scope of data to fetch: either single node or all the descendant nodes
* (recursively) as well
- * @return data node object
+ * @return collection of data node object
*/
- DataNode getDataNode(String dataspaceName, String anchorName, String xpath,
- FetchDescendantsOption fetchDescendantsOption);
+ Collection<DataNode> getDataNodes(String dataspaceName, String anchorName, String xpath,
+ FetchDescendantsOption fetchDescendantsOption);
/**
- * Retrieves datanode by XPath for given dataspace and anchor.
+ * Retrieves multiple datanodes for multiple XPaths, given a dataspace and anchor.
*
- * @param dataspaceName dataspace name
- * @param anchorName anchor name
- * @param xpaths collection of xpaths
- * @param fetchDescendantsOption defines the scope of data to fetch: either single node or all the descendant nodes
- * (recursively) as well
- * @return data node object
- */
- Collection<DataNode> getDataNodes(String dataspaceName, String anchorName, Collection<String> xpaths,
- FetchDescendantsOption fetchDescendantsOption);
+ * @param dataspaceName dataspace name
+ * @param anchorName anchor name
+ * @param xpaths collection of xpaths
+ * @param fetchDescendantsOption defines the scope of data to fetch: either single node or all the descendant nodes
+ * (recursively) as well
+ * @return collection of data node object
+ */
+ Collection<DataNode> getDataNodesForMultipleXpaths(String dataspaceName, String anchorName,
+ Collection<String> xpaths,
+ FetchDescendantsOption fetchDescendantsOption);
/**
* Updates leaves for existing data node.
diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
index 8bbf4e5715..5a74d073aa 100644
--- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
@@ -3,7 +3,7 @@
* Copyright (C) 2021-2023 Nordix Foundation
* Modifications Copyright (C) 2021 Pantheon.tech
* Modifications Copyright (C) 2021-2022 Bell Canada.
- * Modifications Copyright (C) 2022 TechMahindra Ltd.
+ * Modifications Copyright (C) 2022-2023 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.
@@ -184,13 +184,27 @@ class CpsDataServiceImplSpec extends Specification {
thrown(DataValidationException)
}
- def 'Get data node with option #fetchDescendantsOption.'() {
- def xpath = '/xpath'
- def dataNode = new DataNodeBuilder().withXpath(xpath).build()
+ def 'Get all data nodes #scenario.'() {
+ given: 'persistence service returns data for GET request'
+ mockCpsDataPersistenceService.getDataNodes(dataspaceName, anchorName, xpath, fetchDescendantsOption) >> dataNode
+ expect: 'service returns same data if using same parameters'
+ objectUnderTest.getDataNodes(dataspaceName, anchorName, xpath, fetchDescendantsOption) == dataNode
+ where: 'following parameters were used'
+ scenario | xpath | fetchDescendantsOption | dataNode
+ 'with root node xpath and descendants' | '/' | FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS | [new DataNodeBuilder().withXpath('/xpath-1').build(), new DataNodeBuilder().withXpath('/xpath-2').build()]
+ 'with root node xpath and no descendants' | '/' | FetchDescendantsOption.OMIT_DESCENDANTS | [new DataNodeBuilder().withXpath('/xpath-1').build(), new DataNodeBuilder().withXpath('/xpath-2').build()]
+ 'with valid xpath and descendants' | '/xpath'| FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS | [new DataNodeBuilder().withXpath('/xpath').build()]
+ 'with valid xpath and no descendants' | '/xpath'| FetchDescendantsOption.OMIT_DESCENDANTS | [new DataNodeBuilder().withXpath('/xpath').build()]
+ }
+
+ def 'Get all data nodes over multiple xpaths with option #fetchDescendantsOption.'() {
+ def xpath1 = '/xpath-1'
+ def xpath2 = '/xpath-2'
+ def dataNode = [new DataNodeBuilder().withXpath(xpath1).build(), new DataNodeBuilder().withXpath(xpath2).build()]
given: 'persistence service returns data for get data request'
- mockCpsDataPersistenceService.getDataNode(dataspaceName, anchorName, xpath, fetchDescendantsOption) >> dataNode
+ mockCpsDataPersistenceService.getDataNodesForMultipleXpaths(dataspaceName, anchorName, [xpath1, xpath2], fetchDescendantsOption) >> dataNode
expect: 'service returns same data if uses same parameters'
- objectUnderTest.getDataNode(dataspaceName, anchorName, xpath, fetchDescendantsOption) == dataNode
+ objectUnderTest.getDataNodesForMultipleXpaths(dataspaceName, anchorName, [xpath1, xpath2], fetchDescendantsOption) == dataNode
where: 'all fetch options are supported'
fetchDescendantsOption << [FetchDescendantsOption.OMIT_DESCENDANTS, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS]
}
@@ -404,4 +418,4 @@ class CpsDataServiceImplSpec extends Specification {
1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName',
'some-anchorName', 250L)
}
-}
+} \ No newline at end of file
diff --git a/cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdateEventFactorySpec.groovy b/cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdateEventFactorySpec.groovy
index 6f9a148eb2..5dbc2bb04b 100644
--- a/cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdateEventFactorySpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdateEventFactorySpec.groovy
@@ -2,6 +2,7 @@
* ============LICENSE_START=======================================================
* Copyright (c) 2021-2022 Bell Canada.
* Modifications Copyright (c) 2022 Nordix Foundation
+ * Modifications Copyright (C) 2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -50,8 +51,8 @@ class CpsDataUpdateEventFactorySpec extends Specification {
and: 'cps data service returns the data node details'
def xpath = '/xpath'
def dataNode = new DataNodeBuilder().withXpath(xpath).withLeaves(['leafName': 'leafValue']).build()
- mockCpsDataService.getDataNode(
- 'my-dataspace', 'my-anchorname', '/', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
+ mockCpsDataService.getDataNodes(
+ 'my-dataspace', 'my-anchorname', '/', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> [dataNode]
when: 'CPS data updated event is created'
def cpsDataUpdatedEvent = objectUnderTest.createCpsDataUpdatedEvent(anchor,
DateTimeUtility.toOffsetDateTime(inputObservedTimestamp), Operation.CREATE)
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/CpsPersistenceSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/CpsPersistenceSpec.groovy
index 94bcb0a6fe..349f0854ef 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/CpsPersistenceSpec.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/CpsPersistenceSpec.groovy
@@ -1,6 +1,7 @@
/*
* ============LICENSE_START=======================================================
* Copyright (C) 2023 Nordix Foundation
+ * Modifications Copyright (C) 2023 TechMahindra Ltd.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
@@ -36,7 +37,7 @@ class CpsPersistenceSpec extends CpsIntegrationSpecBase{
and: 'The anchor has been persisted successfully'
cpsAdminService.getAnchor(TEST_DATASPACE, TEST_ANCHOR).getName() == TEST_ANCHOR
and: 'The data nodes have been persisted successfully'
- cpsDataService.getDataNode(TEST_DATASPACE, TEST_ANCHOR, '/bookstore', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS).xpath == '/bookstore'
+ cpsDataService.getDataNodes(TEST_DATASPACE, TEST_ANCHOR, '/bookstore', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS).iterator().next().xpath == '/bookstore'
}
def 'Test deletion of all test data'() {
@@ -56,6 +57,6 @@ class CpsPersistenceSpec extends CpsIntegrationSpecBase{
when: 'data is persisted to the database'
saveDataNodes(TEST_DATASPACE, TEST_ANCHOR, "/", "BookstoreDataNodes.json")
then: 'the correct data is saved'
- cpsDataService.getDataNode(TEST_DATASPACE, TEST_ANCHOR, '/bookstore', FetchDescendantsOption.OMIT_DESCENDANTS).leaves['bookstore-name'] == 'Easons'
+ cpsDataService.getDataNodes(TEST_DATASPACE, TEST_ANCHOR, '/bookstore', FetchDescendantsOption.OMIT_DESCENDANTS).iterator().next().leaves['bookstore-name'] == 'Easons'
}
}