summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNiamh Core <niamh.core@est.tech>2021-08-17 15:30:16 +0000
committerGerrit Code Review <gerrit@onap.org>2021-08-17 15:30:16 +0000
commitddf661254f711d78b480b8f5623632443a8f6ebc (patch)
tree719873e2e027621126d69f8cbe431f9468203f43
parent4571ab67d1b3d24d5f046a38055bcf34910cc785 (diff)
parent3724abc1912f93bf1caa104a55da7178f43fd731 (diff)
Merge "Delete list-node p1 service and persistence layers"
-rw-r--r--cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java30
-rwxr-xr-xcps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy66
-rwxr-xr-xcps-ri/src/test/resources/data/fragment.sql12
-rw-r--r--cps-service/src/main/java/org/onap/cps/api/CpsDataService.java22
-rwxr-xr-xcps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java7
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java10
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy12
7 files changed, 133 insertions, 26 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 c638b9113..af010f4fc 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
@@ -34,6 +34,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.transaction.Transactional;
@@ -48,6 +49,7 @@ import org.onap.cps.spi.entities.FragmentEntity;
import org.onap.cps.spi.exceptions.AlreadyDefinedException;
import org.onap.cps.spi.exceptions.ConcurrencyException;
import org.onap.cps.spi.exceptions.CpsPathException;
+import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
import org.onap.cps.spi.model.DataNode;
import org.onap.cps.spi.model.DataNodeBuilder;
import org.onap.cps.spi.repository.AnchorRepository;
@@ -82,6 +84,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
private static final Gson GSON = new GsonBuilder().create();
private static final String REG_EX_FOR_OPTIONAL_LIST_INDEX = "(\\[@[\\s\\S]+?]){0,1})";
+ private static final String REG_EX_FOR_LIST_NODE_KEY = "\\[(\\@([^/]*?))+( and)*\\]$";
@Override
public void addChildDataNode(final String dataspaceName, final String anchorName, final String parentXpath,
@@ -315,8 +318,33 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
fragmentRepository.save(parentEntity);
}
+ @Override
+ @Transactional
+ public void deleteListDataNodes(final String dataspaceName, final String anchorName, final String listNodeXpath) {
+ final var parentNodeXpath = listNodeXpath.substring(0, listNodeXpath.lastIndexOf('/'));
+ final var parentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
+ final var descendantNode = listNodeXpath.substring(listNodeXpath.lastIndexOf('/'));
+ final Matcher descendantNodeHasListNodeKey = Pattern.compile(REG_EX_FOR_LIST_NODE_KEY).matcher(descendantNode);
+
+ final boolean xpathPointsToAValidChildNodeWithKey = parentEntity.getChildFragments().stream().anyMatch(
+ (fragment) -> fragment.getXpath().equals(listNodeXpath));
+
+ final boolean xpathPointsToAValidChildNodeWithoutKey = parentEntity.getChildFragments().stream().anyMatch(
+ (fragment) -> fragment.getXpath().replaceAll(REG_EX_FOR_LIST_NODE_KEY, "").equals(listNodeXpath));
+
+ if ((descendantNodeHasListNodeKey.find() && xpathPointsToAValidChildNodeWithKey)
+ ||
+ (!descendantNodeHasListNodeKey.find() && xpathPointsToAValidChildNodeWithoutKey)) {
+ removeListNodeDescendants(parentEntity, listNodeXpath);
+ } else {
+ throw new DataNodeNotFoundException(parentEntity.getDataspace().getName(),
+ parentEntity.getAnchor().getName(), listNodeXpath);
+ }
+ }
+
private void removeListNodeDescendants(final FragmentEntity parentFragmentEntity, final String listNodeXpath) {
- final String listNodeXpathPrefix = listNodeXpath + "[";
+ final Matcher descendantNodeHasListNodeKey = Pattern.compile(REG_EX_FOR_LIST_NODE_KEY).matcher(listNodeXpath);
+ final String listNodeXpathPrefix = listNodeXpath + (descendantNodeHasListNodeKey.find() ? "" : "[");
if (parentFragmentEntity.getChildFragments()
.removeIf(fragment -> fragment.getXpath().startsWith(listNodeXpathPrefix))) {
fragmentRepository.save(parentFragmentEntity);
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 ad8db766f..8217a4fb0 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
@@ -21,7 +21,7 @@
*/
package org.onap.cps.spi.impl
-import org.onap.cps.spi.exceptions.ConcurrencyException
+import org.onap.cps.spi.exceptions.DataValidationException
import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
@@ -55,7 +55,10 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
static final String XPATH_DATA_NODE_WITH_LEAVES = '/parent-100'
static final long UPDATE_DATA_NODE_FRAGMENT_ID = 4202L
static final long UPDATE_DATA_NODE_SUB_FRAGMENT_ID = 4203L
- static final long LIST_DATA_NODE_PARENT_FRAGMENT_ID = 4206L
+ static final long LIST_DATA_NODE_PARENT201_FRAGMENT_ID = 4206L
+ static final long LIST_DATA_NODE_PARENT203_FRAGMENT_ID = 4214L
+ static final long LIST_DATA_NODE_CHILD202_FRAGMENT_ID = 4204L
+ static final long LIST_DATA_NODE_PARENT202_FRAGMENT_ID = 4211L
static final DataNode newDataNode = new DataNodeBuilder().build()
static DataNode existingDataNode
@@ -159,7 +162,7 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
when: 'list-node elements added to existing parent node'
objectUnderTest.addListDataNodes(DATASPACE_NAME, ANCHOR_NAME3, '/parent-201', listNodeCollection)
then: 'new entries successfully persisted, parent node now contains 5 children (2 new + 3 existing before)'
- def parentFragment = fragmentRepository.getOne(LIST_DATA_NODE_PARENT_FRAGMENT_ID)
+ def parentFragment = fragmentRepository.getById(LIST_DATA_NODE_PARENT201_FRAGMENT_ID)
def allChildXpaths = parentFragment.getChildFragments().collect { it.getXpath() }
assert allChildXpaths.size() == 5
assert allChildXpaths.containsAll(listNodeXpaths)
@@ -253,7 +256,7 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
objectUnderTest.updateDataLeaves(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES,
"/parent-200/child-201", ['leaf-value': 'new'])
then: 'leaves are updated for selected data node'
- def updatedFragment = fragmentRepository.getOne(UPDATE_DATA_NODE_FRAGMENT_ID)
+ def updatedFragment = fragmentRepository.getById(UPDATE_DATA_NODE_FRAGMENT_ID)
def updatedLeaves = getLeavesMap(updatedFragment)
assert updatedLeaves.size() == 1
assert updatedLeaves.'leaf-value' == 'new'
@@ -284,7 +287,7 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
when: 'replace data node tree is performed'
objectUnderTest.replaceDataNodeTree(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, submittedDataNode)
then: 'leaves have been updated for selected data node'
- def updatedFragment = fragmentRepository.getOne(UPDATE_DATA_NODE_FRAGMENT_ID)
+ def updatedFragment = fragmentRepository.getById(UPDATE_DATA_NODE_FRAGMENT_ID)
def updatedLeaves = getLeavesMap(updatedFragment)
assert updatedLeaves.size() == 1
assert updatedLeaves.'leaf-value' == 'new'
@@ -298,12 +301,12 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
def 'Replace data node tree with descendants.'() {
given: 'data node object with leaves updated, having child with old content'
def submittedDataNode = buildDataNode("/parent-200/child-201", ['leaf-value': 'new'], [
- buildDataNode("/parent-200/child-201/grand-child", ['leaf-value': 'original'], [])
+ buildDataNode("/parent-200/child-201/grand-child", ['leaf-value': 'original'], [])
])
when: 'update is performed including descendants'
objectUnderTest.replaceDataNodeTree(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, submittedDataNode)
then: 'leaves have been updated for selected data node'
- def updatedFragment = fragmentRepository.getOne(UPDATE_DATA_NODE_FRAGMENT_ID)
+ def updatedFragment = fragmentRepository.getById(UPDATE_DATA_NODE_FRAGMENT_ID)
def updatedLeaves = getLeavesMap(updatedFragment)
assert updatedLeaves.size() == 1
assert updatedLeaves.'leaf-value' == 'new'
@@ -323,7 +326,7 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
when: 'update is performed including descendants'
objectUnderTest.replaceDataNodeTree(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, submittedDataNode)
then: 'leaves have been updated for selected data node'
- def updatedFragment = fragmentRepository.getOne(UPDATE_DATA_NODE_FRAGMENT_ID)
+ def updatedFragment = fragmentRepository.getById(UPDATE_DATA_NODE_FRAGMENT_ID)
def updatedLeaves = getLeavesMap(updatedFragment)
assert updatedLeaves.size() == 1
assert updatedLeaves.'leaf-value' == 'new'
@@ -343,7 +346,7 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
when: 'update is performed including descendants'
objectUnderTest.replaceDataNodeTree(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, submittedDataNode)
then: 'leaves have been updated for selected data node'
- def updatedFragment = fragmentRepository.getOne(UPDATE_DATA_NODE_FRAGMENT_ID)
+ def updatedFragment = fragmentRepository.getById(UPDATE_DATA_NODE_FRAGMENT_ID)
def updatedLeaves = getLeavesMap(updatedFragment)
assert updatedLeaves.size() == 1
assert updatedLeaves.'leaf-value' == 'new'
@@ -362,7 +365,7 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
def submittedDataNode = buildDataNode(xpath, ['leaf-name': 'leaf-value'], [])
when: 'attempt to update data node for #scenario'
objectUnderTest.replaceDataNodeTree(dataspaceName, anchorName, submittedDataNode)
- then: 'a #expectedException is thrown'
+ then: 'a #expectedException is thrown'
thrown(expectedException)
where: 'the following data is used'
scenario | dataspaceName | anchorName | xpath || expectedException
@@ -378,7 +381,7 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
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 parentFragment = fragmentRepository.getById(LIST_DATA_NODE_PARENT201_FRAGMENT_ID)
def allChildXpaths = parentFragment.getChildFragments().collect { it.getXpath() }
assert allChildXpaths.size() == expectedChildXpaths.size()
assert allChildXpaths.containsAll(expectedChildXpaths)
@@ -401,6 +404,47 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
'parent node does not exist' | '/unknown' | ['irrelevant'] || DataNodeNotFoundException
}
+ @Sql([CLEAR_DATA, SET_DATA])
+ def 'Delete list-node content of #scenario.'() {
+ given: 'list node data fragments are present in database'
+ when: 'list-node elements deleted within the existing parent node'
+ objectUnderTest.deleteListDataNodes(DATASPACE_NAME, ANCHOR_NAME3, listNodeXpaths)
+ then: 'child list elements are removed as expected, non-list element remains as is'
+ def parentFragment = fragmentRepository.getById(listNodeFragmentID)
+ def allChildXpaths = parentFragment.getChildFragments().collect { it.getXpath() }
+ assert allChildXpaths.size() == expectedChildXpaths.size()
+ assert allChildXpaths.containsAll(expectedChildXpaths)
+ where: 'following parameters were used'
+ scenario | listNodeXpaths | listNodeFragmentID || expectedChildXpaths
+ 'existing list-node with key' | '/parent-203/child-204[@key="A"]' | LIST_DATA_NODE_PARENT203_FRAGMENT_ID || ['/parent-203/child-203', '/parent-203/child-204[@key="X"]']
+ 'existing list-node with key' | '/parent-203/child-204[@key="X"]' | LIST_DATA_NODE_PARENT203_FRAGMENT_ID || ['/parent-203/child-203', '/parent-203/child-204[@key="A"]']
+ 'existing grand-child list node with keys' | '/parent-203/child-204[@key="X"]/grand-child-204[@key2="Y"]' | LIST_DATA_NODE_PARENT203_FRAGMENT_ID || ['/parent-203/child-203', '/parent-203/child-204[@key="X"]', '/parent-203/child-204[@key="A"]']
+ 'existing list-node with combined keys' | '/parent-202/child-205[@key="A" and @key2="B"]' | LIST_DATA_NODE_PARENT202_FRAGMENT_ID || ['/parent-202/child-206[@key="A"]']
+ 'existing node with list node variants to delete' | '/parent-203/child-204' | LIST_DATA_NODE_PARENT203_FRAGMENT_ID || ['/parent-203/child-203']
+ 'existing grandchild list-node' | '/parent-200/child-202/grand-child-202[@key="D"]' | LIST_DATA_NODE_CHILD202_FRAGMENT_ID || []
+ }
+
+ @Sql([CLEAR_DATA, SET_DATA])
+ def 'Delete list-node fragment error scenario: #scenario.'() {
+ given: 'list node data fragments are present in database'
+ when: 'list-node elements are deleted under existing parent node'
+ objectUnderTest.deleteListDataNodes(DATASPACE_NAME, ANCHOR_NAME3, listNodeXpaths)
+ then: 'a #expectedException is thrown'
+ thrown(expectedException)
+ where: 'following parameters were used'
+ scenario | listNodeXpaths || expectedException
+ 'list parent node does not exist' | '/unknown/unknown' || DataNodeNotFoundException
+ 'list child nodes do not exist' | '/parent-200/unknown' || DataNodeNotFoundException
+ 'list child nodes with key does not exist' | '/parent-200/unknown[@key="C"]' || DataNodeNotFoundException
+ 'list grandchild nodes parent does not exist' | '/parent-200/unknown/unknown' || DataNodeNotFoundException
+ 'non-existing parent with existing list-node' | '/unknown/child-204' || DataNodeNotFoundException
+ 'non-existing parent with existing list-node & key' | '/unknown/child-204[@key="A"]' || DataNodeNotFoundException
+ 'valid with non existing key' | '/parent-200/child-202/grand-child-202[@key="A"]' || DataNodeNotFoundException
+ 'child list node without key' | '/parent-200/child-204/grand-child-204' || DataNodeNotFoundException
+ 'valid list node with invalid key' | '/parent-203/child-204[@key="C"]' || DataNodeNotFoundException
+
+ }
+
static Collection<DataNode> buildDataNodeCollection(xpaths) {
return xpaths.collect { new DataNodeBuilder().withXpath(it).build() }
}
diff --git a/cps-ri/src/test/resources/data/fragment.sql b/cps-ri/src/test/resources/data/fragment.sql
index d7109f20b..886e6e175 100755
--- a/cps-ri/src/test/resources/data/fragment.sql
+++ b/cps-ri/src/test/resources/data/fragment.sql
@@ -49,8 +49,16 @@ INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH, ATTRIBUTES)
(4202, 1001, 3003, 4201, '/parent-200/child-201', '{"leaf-value": "original"}'),
(4203, 1001, 3003, 4202, '/parent-200/child-201/grand-child', '{"leaf-value": "original"}'),
(4204, 1001, 3003, 4201, '/parent-200/child-202', '{"common-leaf-name": "common-leaf value", "common-leaf-name-int" : 5}'),
- (4205, 1001, 3003, 4204, '/parent-200/child-202/grand-child-202', '{"common-leaf-name": "common-leaf value", "common-leaf-name-int" : 5}'),
+ (4205, 1001, 3003, 4204, '/parent-200/child-202/grand-child-202[@key="D"]', '{"common-leaf-name": "common-leaf value", "common-leaf-name-int" : 5}'),
(4206, 1001, 3003, null, '/parent-201', '{"leaf-value": "original"}'),
(4207, 1001, 3003, 4206, '/parent-201/child-203', '{}'),
(4208, 1001, 3003, 4206, '/parent-201/child-204[@key="A"]', '{"key": "A"}'),
- (4209, 1001, 3003, 4206, '/parent-201/child-204[@key="X"]', '{"key": "X"}');
+ (4209, 1001, 3003, 4206, '/parent-201/child-204[@key="X"]', '{"key": "X"}'),
+ (4211, 1001, 3003, null, '/parent-202', '{"leaf-value": "original"}'),
+ (4212, 1001, 3003, 4211, '/parent-202/child-205[@key="A" and @key2="B"]', '{"key": "A", "key2": "B"}'),
+ (4213, 1001, 3003, 4211, '/parent-202/child-206[@key="A"]', '{"key": "A"}'),
+ (4214, 1001, 3003, null, '/parent-203', '{"leaf-value": "original"}'),
+ (4215, 1001, 3003, 4214, '/parent-203/child-203', '{}'),
+ (4216, 1001, 3003, 4214, '/parent-203/child-204[@key="A"]', '{"key": "A"}'),
+ (4217, 1001, 3003, 4214, '/parent-203/child-204[@key="X"]', '{"key": "X"}'),
+ (4218, 1001, 3003, 4217, '/parent-203/child-204[@key="X"]/grand-child-204[@key2="Y"]', '{"key": "X", "key2": "Y"}'); \ No newline at end of file
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 7187810e4..6036f9222 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
@@ -23,9 +23,6 @@ package org.onap.cps.api;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.onap.cps.spi.FetchDescendantsOption;
-import org.onap.cps.spi.exceptions.AlreadyDefinedException;
-import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
-import org.onap.cps.spi.exceptions.DataValidationException;
import org.onap.cps.spi.model.DataNode;
/*
@@ -39,7 +36,6 @@ public interface CpsDataService {
* @param dataspaceName dataspace name
* @param anchorName anchor name
* @param jsonData json data
- * @throws DataValidationException when json data is invalid
*/
void saveData(@NonNull String dataspaceName, @NonNull String anchorName, @NonNull String jsonData);
@@ -50,9 +46,6 @@ public interface CpsDataService {
* @param anchorName anchor name
* @param parentNodeXpath parent node xpath
* @param jsonData json data
- * @throws DataValidationException when json data is invalid
- * @throws DataNodeNotFoundException when parent node cannot be found by parent node xpath
- * @throws AlreadyDefinedException when child data node with same xpath already exists
*/
void saveData(@NonNull String dataspaceName, @NonNull String anchorName, @NonNull String parentNodeXpath,
@NonNull String jsonData);
@@ -65,9 +58,6 @@ public interface CpsDataService {
* @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
- * @throws AlreadyDefinedException when any of child data nodes is having xpath of already existing node
*/
void saveListNodeData(@NonNull String dataspaceName, @NonNull String anchorName, @NonNull String parentNodeXpath,
@NonNull String jsonData);
@@ -115,9 +105,17 @@ public interface CpsDataService {
* @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);
+
+ /**
+ * Deletes (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 listNodeXpath list node xpath
+ */
+ void deleteListNodeData(@NonNull String dataspaceName, @NonNull String anchorName, @NonNull String listNodeXpath);
}
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 717df16a5..5e6e1a268 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
@@ -114,6 +114,13 @@ public class CpsDataServiceImpl implements CpsDataService {
notificationService.processDataUpdatedEvent(dataspaceName, anchorName);
}
+ @Override
+ public void deleteListNodeData(final String dataspaceName, final String anchorName, final String listNodeXpath) {
+ cpsDataPersistenceService.deleteListDataNodes(dataspaceName, anchorName, listNodeXpath);
+ notificationService.processDataUpdatedEvent(dataspaceName, anchorName);
+ }
+
+
private DataNode buildDataNodeFromJson(final String dataspaceName, final String anchorName,
final String parentNodeXpath, final String jsonData) {
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 cfb39f595..bf8dd1a07 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
@@ -112,6 +112,16 @@ public interface CpsDataPersistenceService {
@NonNull String parentNodeXpath, @NonNull Collection<DataNode> dataNodes);
/**
+ * Deletes existing list data node content including descendants.
+ *
+ * @param dataspaceName dataspace name
+ * @param anchorName anchor name
+ * @param listNodeXpath list node xpath
+ */
+ void deleteListDataNodes(@NonNull String dataspaceName, @NonNull String anchorName,
+ @NonNull String listNodeXpath);
+
+ /**
* 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 19ccee367..122039728 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
@@ -28,6 +28,7 @@ import org.onap.cps.api.CpsModuleService
import org.onap.cps.notification.NotificationService
import org.onap.cps.spi.CpsDataPersistenceService
import org.onap.cps.spi.FetchDescendantsOption
+import org.onap.cps.spi.exceptions.CpsPathException
import org.onap.cps.spi.exceptions.DataValidationException
import org.onap.cps.spi.model.Anchor
import org.onap.cps.spi.model.DataNodeBuilder
@@ -197,6 +198,17 @@ class CpsDataServiceImplSpec extends Specification {
thrown(DataValidationException)
}
+ def 'Delete list-node data fragment under existing node.'() {
+ given: 'schema set for given anchor and dataspace references test-tree model'
+ setupSchemaSetMocks('test-tree.yang')
+ when: 'delete list data method is invoked with list-node json data'
+ objectUnderTest.deleteListNodeData(dataspaceName, anchorName, '/test-tree/branch')
+ then: 'the persistence service method is invoked with correct parameters'
+ 1 * mockCpsDataPersistenceService.deleteListDataNodes(dataspaceName, anchorName, '/test-tree/branch')
+ and: 'data updated event is sent to notification service'
+ 1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName)
+ }
+
def setupSchemaSetMocks(String... yangResources) {
def anchor = Anchor.builder().name(anchorName).schemaSetName(schemaSetName).build()
mockCpsAdminService.getAnchor(dataspaceName, anchorName) >> anchor