From c7e5a80d6f11b76d35341bf7d934c6a06b783e01 Mon Sep 17 00:00:00 2001 From: --global Date: Wed, 5 Apr 2023 17:08:08 +0530 Subject: Support for Patch across multiple data nodes - Added new method updateMultipleDataLeaves to perform Update on multiple data nodes - Deprecated singular method of update data node(updateDataLeaves) - Refactored code where singular version was used - Updated release notes Issue-ID: CPS-1006 Signed-off-by: Change-Id: If67280e2dd3ad566de9a8217489f168415e624bc --- .../main/java/org/onap/cps/api/CpsDataService.java | 12 ++++----- .../org/onap/cps/api/impl/CpsDataServiceImpl.java | 18 ++++++-------- .../onap/cps/spi/CpsDataPersistenceService.java | 12 ++++----- .../cps/api/impl/CpsDataServiceImplSpec.groovy | 29 +++++++++++++++------- 4 files changed, 39 insertions(+), 32 deletions(-) (limited to 'cps-service') 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 fc008685c1..6a2cac4679 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 @@ -138,9 +138,7 @@ public interface CpsDataService { FetchDescendantsOption fetchDescendantsOption); /** - * Updates data node for given dataspace and anchor using xpath to parent node. This method can currently - * update only one top level data node. The method will throw DataValidationException when more than one top level - * data nodes are provided in jsonData + * Updates multiple data nodes for given dataspace and anchor using xpath to parent node. * * @param dataspaceName dataspace name * @param anchorName anchor name @@ -154,10 +152,10 @@ public interface CpsDataService { /** * Replaces an existing data node's content including descendants. * - * @param dataspaceName dataspace name - * @param anchorName anchor name - * @param parentNodeXpath xpath to parent node - * @param jsonData json data + * @param dataspaceName dataspace name + * @param anchorName anchor name + * @param parentNodeXpath xpath to parent node + * @param jsonData json data * @param observedTimestamp observedTimestamp */ void updateDataNodeAndDescendants(String dataspaceName, String anchorName, String parentNodeXpath, String jsonData, 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 51e31f08c8..99cda229db 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 @@ -29,8 +29,10 @@ import static org.onap.cps.notification.Operation.DELETE; import static org.onap.cps.notification.Operation.UPDATE; import io.micrometer.core.annotation.Timed; +import java.io.Serializable; import java.time.OffsetDateTime; import java.util.Collection; +import java.util.Collections; import java.util.Map; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @@ -155,20 +157,16 @@ public class CpsDataServiceImpl implements CpsDataService { @Override @Timed(value = "cps.data.service.datanode.leaves.update", - description = "Time taken to get a batch of data nodes") + description = "Time taken to update a batch of leaf data nodes") public void updateNodeLeaves(final String dataspaceName, final String anchorName, final String parentNodeXpath, final String jsonData, final OffsetDateTime observedTimestamp) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName); final Collection dataNodesInPatch = buildDataNodes(anchor, parentNodeXpath, jsonData, ContentType.JSON); - if (dataNodesInPatch.size() > 1) { - throw new DataValidationException("Operation is not supported for multiple data nodes", - "Number of data nodes present: " + dataNodesInPatch.size()); - } - cpsDataPersistenceService.updateDataLeaves(dataspaceName, anchorName, - dataNodesInPatch.iterator().next().getXpath(), - dataNodesInPatch.iterator().next().getLeaves()); + final Map> xpathToUpdatedLeaves = dataNodesInPatch.stream() + .collect(Collectors.toMap(DataNode::getXpath, DataNode::getLeaves)); + cpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, xpathToUpdatedLeaves); processDataUpdatedEventAsync(anchor, parentNodeXpath, UPDATE, observedTimestamp); } @@ -395,8 +393,8 @@ public class CpsDataServiceImpl implements CpsDataService { if (dataNodeUpdate == null) { return; } - cpsDataPersistenceService.updateDataLeaves(anchor.getDataspaceName(), anchor.getName(), - dataNodeUpdate.getXpath(), dataNodeUpdate.getLeaves()); + cpsDataPersistenceService.batchUpdateDataLeaves(anchor.getDataspaceName(), anchor.getName(), + Collections.singletonMap(dataNodeUpdate.getXpath(), dataNodeUpdate.getLeaves())); final Collection childDataNodeUpdates = dataNodeUpdate.getChildDataNodes(); for (final DataNode childDataNodeUpdate : childDataNodeUpdates) { processDataNodeUpdate(anchor, childDataNodeUpdate); 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 d28a3339fe..4fb25e949a 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 @@ -116,14 +116,14 @@ public interface CpsDataPersistenceService { FetchDescendantsOption fetchDescendantsOption); /** - * Updates leaves for existing data node. + * Updates data leaves for multiple data nodes. * - * @param dataspaceName dataspace name - * @param anchorName anchor name - * @param xpath xpath - * @param leaves the leaves as a map where key is a leaf name and a value is a leaf value + * @param dataspaceName dataspace name + * @param anchorName anchor name + * @param updatedLeavesPerXPath Map of xPaths to updated leaf nodes */ - void updateDataLeaves(String dataspaceName, String anchorName, String xpath, Map leaves); + void batchUpdateDataLeaves(String dataspaceName, String anchorName, + Map> updatedLeavesPerXPath); /** * Replaces multiple existing data nodes' content including descendants in a batch operation. 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 be397b92cd..e357d24625 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 @@ -215,15 +215,15 @@ class CpsDataServiceImplSpec extends Specification { when: 'update data method is invoked with json data #jsonData and parent node xpath #parentNodeXpath' objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath, jsonData, observedTimestamp) then: 'the persistence service method is invoked with correct parameters' - 1 * mockCpsDataPersistenceService.updateDataLeaves(dataspaceName, anchorName, expectedNodeXpath, leaves) + 1 * mockCpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, {dataNode -> dataNode.keySet()[0] == expectedNodeXpath}) and: 'the CpsValidator is called on the dataspaceName and AnchorName' 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) and: 'data updated event is sent to notification service' 1 * mockNotificationService.processDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp) where: 'following parameters were used' - scenario | parentNodeXpath | jsonData || expectedNodeXpath | leaves - 'top level node' | '/' | '{"test-tree": {"branch": []}}' || '/test-tree' | Collections.emptyMap() - 'level 2 node' | '/test-tree' | '{"branch": [{"name":"Name"}]}' || '/test-tree/branch[@name=\'Name\']' | ['name': 'Name'] + scenario | parentNodeXpath | jsonData || expectedNodeXpath + 'top level node' | '/' | '{"test-tree": {"branch": []}}' || '/test-tree' + 'level 2 node' | '/test-tree' | '{"branch": [{"name":"Name"}]}' || '/test-tree/branch[@name=\'Name\']' } def 'Update list-element data node with : #scenario.'() { @@ -244,11 +244,21 @@ class CpsDataServiceImplSpec extends Specification { given: 'schema set for given dataspace and anchor refers multipleDataTree model' setupSchemaSetMocks('multipleDataTree.yang') and: 'json string with multiple data trees' + def parentNodeXpath = '/' def updatedJsonData = '{"first-container":{"a-leaf":"a-new-Value"},"last-container":{"x-leaf":"x-new-value"}}' when: 'update operation is performed on multiple data nodes' - objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, '/', updatedJsonData, observedTimestamp) - then: 'expected exception is thrown' - thrown(DataValidationException) + objectUnderTest.updateNodeLeaves(dataspaceName, anchorName, parentNodeXpath, updatedJsonData, observedTimestamp) + then: 'the persistence service method is invoked with correct parameters' + 1 * mockCpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, {dataNode -> dataNode.keySet()[index] == expectedNodeXpath}) + and: 'the CpsValidator is called on the dataspaceName and AnchorName' + 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) + and: 'data updated event is sent to notification service' + 1 * mockNotificationService.processDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp) + where: 'the following parameters were used' + index | expectedNodeXpath + 0 | '/first-container' + 1 | '/last-container' + } def 'Update Bookstore node leaves' () { @@ -260,8 +270,9 @@ class CpsDataServiceImplSpec extends Specification { objectUnderTest.updateNodeLeavesAndExistingDescendantLeaves(dataspaceName, anchorName, '/bookstore', jsonData, observedTimestamp) then: 'the persistence service method is invoked with correct parameters' - 1 * mockCpsDataPersistenceService.updateDataLeaves(dataspaceName, anchorName, - "/bookstore/categories[@code='01']", ['name':'Romance', 'code': '01']) + 1 * mockCpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, + {updatedDataNodesPerXPath -> updatedDataNodesPerXPath.keySet() + .iterator().next() == "/bookstore/categories[@code='01']"}) and: 'the CpsValidator is called on the dataspaceName and AnchorName' 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) and: 'the data updated event is sent to the notification service' -- cgit 1.2.3-korg