summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRuslan Kashapov <ruslan.kashapov@pantheon.tech>2021-05-14 12:04:34 +0300
committerRishi Chail <rishi.chail@est.tech>2021-05-24 15:36:55 +0000
commitb0e78acb6aefdcb5866955802099e6091b2a6952 (patch)
tree8f06ec09b2d9fba00dc8dfe4fbbe639330417c15
parent940d8e4a950d8ce8075a712d9dc3852de828e5b5 (diff)
Replace list-node content (part 1): CPS Service and persistence layers
Issue-ID: CPS-362 Change-Id: I669c9fc6ef67c1992fe95e17a765f0c616b00f7e Signed-off-by: Ruslan Kashapov <ruslan.kashapov@pantheon.tech>
-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/CpsDataPersistenceServiceSpec.groovy30
-rw-r--r--cps-service/src/main/java/org/onap/cps/api/CpsDataService.java14
-rwxr-xr-xcps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java18
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java11
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy28
6 files changed, 122 insertions, 4 deletions
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 ae399a138..10440924e 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
@@ -35,6 +35,7 @@ import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
+import javax.transaction.Transactional;
import org.onap.cps.spi.CpsDataPersistenceService;
import org.onap.cps.spi.FetchDescendantsOption;
import org.onap.cps.spi.entities.AnchorEntity;
@@ -253,6 +254,30 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
fragmentRepository.save(fragmentEntity);
}
+ @Override
+ @Transactional
+ public void replaceListDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath,
+ final Collection<DataNode> dataNodes) {
+ final var parentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
+ final var firstChildNodeXpath = dataNodes.iterator().next().getXpath();
+ final var listNodeXpath = firstChildNodeXpath.substring(0, firstChildNodeXpath.lastIndexOf("["));
+ removeListNodeDescendants(parentEntity, listNodeXpath);
+ final Set<FragmentEntity> childFragmentEntities = dataNodes.stream().map(
+ dataNode -> convertToFragmentWithAllDescendants(
+ parentEntity.getDataspace(), parentEntity.getAnchor(), dataNode)
+ ).collect(Collectors.toUnmodifiableSet());
+ parentEntity.getChildFragments().addAll(childFragmentEntities);
+ fragmentRepository.save(parentEntity);
+ }
+
+ private void removeListNodeDescendants(final FragmentEntity parentFragmentEntity, final String listNodeXpath) {
+ final String listNodeXpathPrefix = listNodeXpath + "[";
+ if (parentFragmentEntity.getChildFragments()
+ .removeIf(fragment -> fragment.getXpath().startsWith(listNodeXpathPrefix))) {
+ fragmentRepository.save(parentFragmentEntity);
+ }
+ }
+
private void removeExistingDescendants(final FragmentEntity fragmentEntity) {
fragmentEntity.setChildFragments(Collections.emptySet());
fragmentRepository.save(fragmentEntity);
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 0f0b1b430..1f2ea6d36 100755
--- 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
@@ -328,6 +328,36 @@ class CpsDataPersistenceServiceSpec extends CpsPersistenceSpecBase {
'non-existing xpath' | DATASPACE_NAME | ANCHOR_FOR_DATA_NODES_WITH_LEAVES | 'NON-EXISTING XPATH' || DataNodeNotFoundException
}
+ @Sql([CLEAR_DATA, SET_DATA])
+ def 'Replace list-node content of #scenario.'() {
+ given: 'list node data fragment as a collection of data nodes'
+ def listNodeCollection = buildDataNodeCollection(listNodeXpaths)
+ when: 'list-node elements replaced within the existing parent node'
+ objectUnderTest.replaceListDataNodes(DATASPACE_NAME, ANCHOR_NAME3, '/parent-201', listNodeCollection)
+ then: 'child list elements are updated as expected, non-list element remains as is'
+ def parentFragment = fragmentRepository.getOne(LIST_DATA_NODE_PARENT_FRAGMENT_ID)
+ def allChildXpaths = parentFragment.getChildFragments().collect { it.getXpath() }
+ assert allChildXpaths.size() == expectedChildXpaths.size()
+ assert allChildXpaths.containsAll(expectedChildXpaths)
+ where: 'following parameters were used'
+ scenario | listNodeXpaths || expectedChildXpaths
+ 'existing list-node' | ['/parent-201/child-204[@key="B"]'] || ['/parent-201/child-203', '/parent-201/child-204[@key="B"]']
+ 'non-existing list-node' | ['/parent-201/child-205[@key="1"]'] || ['/parent-201/child-203', '/parent-201/child-204[@key="A"]', '/parent-201/child-204[@key="X"]', '/parent-201/child-205[@key="1"]']
+ }
+
+ @Sql([CLEAR_DATA, SET_DATA])
+ def 'Replace list-node fragment error scenario: #scenario.'() {
+ given: 'list node data fragment as a collection of data nodes'
+ def listNodeCollection = buildDataNodeCollection(listNodeXpaths)
+ when: 'list-node elements were replaced under existing parent node'
+ objectUnderTest.replaceListDataNodes(DATASPACE_NAME, ANCHOR_NAME3, parentNodeXpath, listNodeCollection)
+ then: 'a #expectedException is thrown'
+ thrown(expectedException)
+ where: 'following parameters were used'
+ scenario | parentNodeXpath | listNodeXpaths || expectedException
+ 'parent node does not exist' | '/unknown' | ['irrelevant'] || DataNodeNotFoundException
+ }
+
static Collection<DataNode> buildDataNodeCollection(xpaths) {
return xpaths.collect { new DataNodeBuilder().withXpath(it).build() }
}
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 8e59ebcbb..3b50c5144 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
@@ -105,4 +105,18 @@ public interface CpsDataService {
*/
void replaceNodeTree(@NonNull String dataspaceName, @NonNull String anchorName, @NonNull String parentNodeXpath,
@NonNull String jsonData);
+
+ /**
+ * Replaces (if exists) child data fragment representing list-node (with one or more elements)
+ * under existing data node for the given anchor and dataspace.
+ *
+ * @param dataspaceName dataspace name
+ * @param anchorName anchor name
+ * @param parentNodeXpath parent node xpath
+ * @param jsonData json data representing list element
+ * @throws DataValidationException when json data is invalid (incl. list-node being empty)
+ * @throws DataNodeNotFoundException when parent node cannot be found by parent node xpath
+ */
+ void replaceListNodeData(@NonNull String dataspaceName, @NonNull String anchorName, @NonNull String parentNodeXpath,
+ @NonNull 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 523657a7f..23bf4f2ee 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
@@ -71,9 +71,6 @@ public class CpsDataServiceImpl implements CpsDataService {
final String parentNodeXpath, final String jsonData) {
final Collection<DataNode> dataNodesCollection =
buildDataNodeCollectionFromJson(dataspaceName, anchorName, parentNodeXpath, jsonData);
- if (dataNodesCollection.isEmpty()) {
- throw new DataValidationException("Invalid list data.", "List node is empty.");
- }
cpsDataPersistenceService.addListDataNodes(dataspaceName, anchorName, parentNodeXpath, dataNodesCollection);
}
@@ -98,6 +95,14 @@ public class CpsDataServiceImpl implements CpsDataService {
cpsDataPersistenceService.replaceDataNodeTree(dataspaceName, anchorName, dataNode);
}
+ @Override
+ public void replaceListNodeData(final String dataspaceName, final String anchorName, final String parentNodeXpath,
+ final String jsonData) {
+ final Collection<DataNode> dataNodes =
+ buildDataNodeCollectionFromJson(dataspaceName, anchorName, parentNodeXpath, jsonData);
+ cpsDataPersistenceService.replaceListDataNodes(dataspaceName, anchorName, parentNodeXpath, dataNodes);
+ }
+
private DataNode buildDataNodeFromJson(final String dataspaceName, final String anchorName,
final String parentNodeXpath, final String jsonData) {
@@ -123,10 +128,15 @@ public class CpsDataServiceImpl implements CpsDataService {
final var schemaContext = getSchemaContext(dataspaceName, anchor.getSchemaSetName());
final NormalizedNode<?, ?> normalizedNode = YangUtils.parseJsonData(jsonData, schemaContext, parentNodeXpath);
- return new DataNodeBuilder()
+ final Collection<DataNode> dataNodes = new DataNodeBuilder()
.withParentNodeXpath(parentNodeXpath)
.withNormalizedNodeTree(normalizedNode)
.buildCollection();
+ if (dataNodes.isEmpty()) {
+ throw new DataValidationException("Invalid list data.", "List node is empty.");
+ }
+ return dataNodes;
+
}
private SchemaContext getSchemaContext(final String dataspaceName, final String schemaSetName) {
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 0ed3bf005..3b16b0d81 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
@@ -101,6 +101,17 @@ public interface CpsDataPersistenceService {
void replaceDataNodeTree(@NonNull String dataspaceName, @NonNull String anchorName, @NonNull DataNode dataNode);
/**
+ * Replaces existing list data node content including descendants.
+ *
+ * @param dataspaceName dataspace name
+ * @param anchorName anchor name
+ * @param parentNodeXpath parent node xpath
+ * @param dataNodes collection of data nodes representing list node elements
+ */
+ void replaceListDataNodes(@NonNull String dataspaceName, @NonNull String anchorName,
+ @NonNull String parentNodeXpath, @NonNull Collection<DataNode> dataNodes);
+
+ /**
* Get a datanode by cps path.
*
* @param dataspaceName dataspace name
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 5f930a152..27a5a4e0c 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
@@ -139,6 +139,34 @@ class CpsDataServiceImplSpec extends Specification {
'level 2 node' | '/test-tree' | '{"branch": [{"name":"Name"}]}' || '/test-tree/branch[@name=\'Name\']'
}
+ def 'Replace list-node data fragment under existing node.'() {
+ given: 'schema set for given anchor and dataspace references test-tree model'
+ setupSchemaSetMocks('test-tree.yang')
+ when: 'replace list data method is invoked with list-node json data'
+ def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
+ objectUnderTest.replaceListNodeData(dataspaceName, anchorName, '/test-tree', jsonData)
+ then: 'the persistence service method is invoked with correct parameters'
+ 1 * mockCpsDataPersistenceService.replaceListDataNodes(dataspaceName, anchorName, '/test-tree',
+ { dataNodeCollection ->
+ {
+ assert dataNodeCollection.size() == 2
+ assert dataNodeCollection.collect { it.getXpath() }
+ .containsAll(['/test-tree/branch[@name=\'A\']', '/test-tree/branch[@name=\'B\']'])
+ }
+ }
+ )
+ }
+
+ def 'Replace with empty list-node data fragment.'() {
+ given: 'schema set for given anchor and dataspace references test-tree model'
+ setupSchemaSetMocks('test-tree.yang')
+ when: 'replace list data method is invoked with empty list-node data fragment'
+ def jsonData = '{"branch": []}'
+ objectUnderTest.replaceListNodeData(dataspaceName, anchorName, '/test-tree', jsonData)
+ then: 'invalid data exception is thrown'
+ thrown(DataValidationException)
+ }
+
def setupSchemaSetMocks(String... yangResources) {
def anchor = Anchor.builder().name(anchorName).schemaSetName(schemaSetName).build()
mockCpsAdminService.getAnchor(dataspaceName, anchorName) >> anchor