summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorToine Siebelink <toine.siebelink@est.tech>2021-11-02 14:15:17 +0000
committerGerrit Code Review <gerrit@onap.org>2021-11-02 14:15:17 +0000
commit6fda688fa63ea7ccd450002fb94a18b07095bea9 (patch)
tree10948449183a46e790e06c1973aa7c6d455b8158
parent5ae0e346118103b31f409188c4c3010933f6bde2 (diff)
parenta79c9f1bdf335843c29a425da53c15b5e353e5a3 (diff)
Merge "Clean Up Code around List Nodes"
-rwxr-xr-xcps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java6
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy20
-rw-r--r--cps-rest/docs/openapi/cpsData.yml22
-rw-r--r--cps-rest/docs/openapi/openapi.yml2
-rwxr-xr-xcps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java16
-rwxr-xr-xcps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy45
-rw-r--r--cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java170
-rwxr-xr-xcps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy209
-rwxr-xr-xcps-ri/src/test/resources/data/fragment.sql7
-rw-r--r--cps-service/src/main/java/org/onap/cps/api/CpsDataService.java34
-rwxr-xr-xcps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java47
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java41
-rwxr-xr-xcps-service/src/main/java/org/onap/cps/spi/exceptions/DataNodeNotFoundException.java15
-rw-r--r--cps-service/src/main/java/org/onap/cps/utils/DataMapUtils.java6
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy39
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/model/DataNodeBuilderSpec.groovy4
-rwxr-xr-xcps-service/src/test/groovy/org/onap/cps/spi/exceptions/CpsExceptionsSpec.groovy7
17 files changed, 389 insertions, 301 deletions
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 11d1a89652..7020c74210 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
@@ -135,7 +135,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
@Override
public void addListNodeElements(final String cmHandle, final String parentNodeXpath, final String jsonData) {
- cpsDataService.saveListNodeData(NF_PROXY_DATASPACE_NAME, cmHandle, parentNodeXpath, jsonData, NO_TIMESTAMP);
+ cpsDataService.saveListElements(NF_PROXY_DATASPACE_NAME, cmHandle, parentNodeXpath, jsonData, NO_TIMESTAMP);
}
@Override
@@ -340,7 +340,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
private void registerAndSyncNewCmHandles(final PersistenceCmHandlesList persistenceCmHandlesList)
throws JsonProcessingException {
final String cmHandleJsonData = objectMapper.writeValueAsString(persistenceCmHandlesList);
- cpsDataService.saveListNodeData(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, "/dmi-registry",
+ cpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, "/dmi-registry",
cmHandleJsonData, NO_TIMESTAMP);
for (final PersistenceCmHandle persistenceCmHandle : persistenceCmHandlesList.getPersistenceCmHandles()) {
@@ -369,7 +369,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
private void parseAndRemoveCmHandlesInDmiRegistration(final DmiPluginRegistration dmiPluginRegistration) {
for (final String cmHandle : dmiPluginRegistration.getRemovedCmHandles()) {
try {
- cpsDataService.deleteListNodeData(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+ cpsDataService.deleteListOrListElement(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
"/dmi-registry/cm-handles[@id='" + cmHandle + "']", NO_TIMESTAMP);
} catch (final DataNodeNotFoundException e) {
log.warn("Datanode {} not deleted message {}", cmHandle, e.getMessage());
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 1bad8ce0ba..ae252b870e 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
@@ -119,7 +119,7 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
when: 'addListNodeElements is invoked'
objectUnderTest.addListNodeElements(cmHandle, xpath, jsonData)
then: 'the CPS service method is invoked once with the expected parameters'
- 1 * mockCpsDataService.saveListNodeData(expectedDataspaceName, cmHandle, xpath, jsonData, noTimestamp)
+ 1 * mockCpsDataService.saveListElements(expectedDataspaceName, cmHandle, xpath, jsonData, noTimestamp)
}
def 'Update data node leaves.'() {
@@ -155,18 +155,18 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
def expectedJsonData = '{"cm-handles":[{"id":"123","dmi-service-name":"my-server","additional-properties":[{"name":"name1","value":"value1"},{"name":"name2","value":"value2"}]}]}'
when: 'registration is updated'
objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
- then: 'the CPS save list node data is invoked with the expected parameters'
- expectedCallsToSaveNode * mockCpsDataService.saveListNodeData('NCMP-Admin', 'ncmp-dmi-registry',
+ then: 'cps save list elements is invoked with the expected parameters'
+ expectedCallsToSaveNode * mockCpsDataService.saveListElements('NCMP-Admin', 'ncmp-dmi-registry',
'/dmi-registry', expectedJsonData, noTimestamp)
- and: 'update Node and Child Data Nodes is invoked with correct parameters'
+ and: 'update node and child data nodes is invoked with correct parameters'
expectedCallsToUpdateNode * mockCpsDataService.updateNodeLeavesAndExistingDescendantLeaves('NCMP-Admin',
'ncmp-dmi-registry', '/dmi-registry', expectedJsonData, noTimestamp)
- and : 'delete list data node is invoked with the correct parameters'
- expectedCallsToDeleteListDataNode * mockCpsDataService.deleteListNodeData('NCMP-Admin',
+ and : 'delete list or list element is invoked with the correct parameters'
+ expectedCallsToDeleteListElement * mockCpsDataService.deleteListOrListElement('NCMP-Admin',
'ncmp-dmi-registry', "/dmi-registry/cm-handles[@id='cmHandle001']", noTimestamp)
where:
- scenario | createdCmHandles | updatedCmHandles | removedCmHandles || expectedCallsToSaveNode | expectedCallsToUpdateNode | expectedCallsToDeleteListDataNode
+ scenario | createdCmHandles | updatedCmHandles | removedCmHandles || expectedCallsToSaveNode | expectedCallsToUpdateNode | expectedCallsToDeleteListElement
'create' | [persistenceCmHandle] | [] | [] || 1 | 0 | 0
'update' | [] | [persistenceCmHandle] | [] || 0 | 1 | 0
'delete' | [] | [] | cmHandlesArray || 0 | 0 | 1
@@ -185,8 +185,8 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
def expectedJsonData = '{"cm-handles":[{"id":"123","dmi-service-name":"my-server","additional-properties":[]}]}'
when: 'registration is updated'
objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
- then: 'the CPS save list node data is invoked with the expected parameters'
- 1 * mockCpsDataService.saveListNodeData('NCMP-Admin', 'ncmp-dmi-registry',
+ then: 'the cps save list element is invoked with the expected parameters'
+ 1 * mockCpsDataService.saveListElements('NCMP-Admin', 'ncmp-dmi-registry',
'/dmi-registry', expectedJsonData, noTimestamp)
}
@@ -214,7 +214,7 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
def dmiPluginRegistration = new DmiPluginRegistration()
dmiPluginRegistration.removedCmHandles = ['some cm handle']
and: 'an JSON processing exception occurs'
- mockCpsDataService.deleteListNodeData(*_) >> { throw (new DataNodeNotFoundException('','')) }
+ mockCpsDataService.deleteListOrListElement(*_) >> { throw (new DataNodeNotFoundException('','')) }
when: 'registration is updated'
objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
then: 'no exception is thrown'
diff --git a/cps-rest/docs/openapi/cpsData.yml b/cps-rest/docs/openapi/cpsData.yml
index d456f44e6c..ca21df53db 100644
--- a/cps-rest/docs/openapi/cpsData.yml
+++ b/cps-rest/docs/openapi/cpsData.yml
@@ -46,13 +46,13 @@ nodeByDataspaceAndAnchor:
$ref: 'components.yml#/components/responses/NotFound'
x-codegen-request-body-name: xpath
-listNodeByDataspaceAndAnchor:
+listElementByDataspaceAndAnchor:
post:
- description: Add list-node child elements to existing node for a given anchor and dataspace
+ description: Add list element(s) to a list for a given anchor and dataspace
tags:
- cps-data
- summary: Add list-node child element(s) under existing parent node
- operationId: addListNodeElements
+ summary: Add list element(s)
+ operationId: addListElements
parameters:
- $ref: 'components.yml#/components/parameters/dataspaceNameInPath'
- $ref: 'components.yml#/components/parameters/anchorNameInPath'
@@ -75,11 +75,11 @@ listNodeByDataspaceAndAnchor:
$ref: 'components.yml#/components/responses/Forbidden'
put:
- description: Replace list-node child elements under existing node for a given anchor and dataspace
+ description: Replace list content under a given parent, anchor and dataspace
tags:
- cps-data
- summary: Replace list-node child element(s) under existing parent node
- operationId: replaceListNodeElements
+ summary: Replace list content
+ operationId: replaceListContent
parameters:
- $ref: 'components.yml#/components/parameters/dataspaceNameInPath'
- $ref: 'components.yml#/components/parameters/anchorNameInPath'
@@ -102,11 +102,11 @@ listNodeByDataspaceAndAnchor:
$ref: 'components.yml#/components/responses/Forbidden'
delete:
- description: Delete list-node child elements under existing node for a given anchor and dataspace
+ description: Delete one or all list element(s) for a given anchor and dataspace
tags:
- cps-data
- summary: Delete list-node child element(s) under existing parent node
- operationId: deleteListNodeElements
+ summary: Delete one or all list element(s)
+ operationId: deleteListOrListElement
parameters:
- $ref: 'components.yml#/components/parameters/dataspaceNameInPath'
- $ref: 'components.yml#/components/parameters/anchorNameInPath'
@@ -202,4 +202,4 @@ nodesByDataspaceAndAnchor:
'401':
$ref: 'components.yml#/components/responses/Unauthorized'
'403':
- $ref: 'components.yml#/components/responses/Forbidden' \ No newline at end of file
+ $ref: 'components.yml#/components/responses/Forbidden'
diff --git a/cps-rest/docs/openapi/openapi.yml b/cps-rest/docs/openapi/openapi.yml
index f9881fb151..76bdb80c23 100644
--- a/cps-rest/docs/openapi/openapi.yml
+++ b/cps-rest/docs/openapi/openapi.yml
@@ -66,7 +66,7 @@ paths:
$ref: 'cpsData.yml#/nodesByDataspaceAndAnchor'
/v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/list-nodes:
- $ref: 'cpsData.yml#/listNodeByDataspaceAndAnchor'
+ $ref: 'cpsData.yml#/listElementByDataspaceAndAnchor'
/v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes/query:
$ref: 'cpsQuery.yml#/nodesByDataspaceAndAnchorAndCpsPath'
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 7db4e5a1b6..f29ead9e9f 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
@@ -60,9 +60,9 @@ public class DataRestController implements CpsDataApi {
}
@Override
- public ResponseEntity<String> addListNodeElements(final String parentNodeXpath,
+ public ResponseEntity<String> addListElements(final String parentNodeXpath,
final String dataspaceName, final String anchorName, final String jsonData, final String observedTimestamp) {
- cpsDataService.saveListNodeData(dataspaceName, anchorName, parentNodeXpath, jsonData,
+ cpsDataService.saveListElements(dataspaceName, anchorName, parentNodeXpath, jsonData,
toOffsetDateTime(observedTimestamp));
return new ResponseEntity<>(HttpStatus.CREATED);
}
@@ -94,19 +94,19 @@ public class DataRestController implements CpsDataApi {
}
@Override
- public ResponseEntity<String> replaceListNodeElements(final String parentNodeXpath,
+ public ResponseEntity<String> replaceListContent(final String parentNodeXpath,
final String dataspaceName, final String anchorName, final String jsonData,
final String observedTimestamp) {
- cpsDataService.replaceListNodeData(dataspaceName, anchorName, parentNodeXpath, jsonData,
+ cpsDataService.replaceListContent(dataspaceName, anchorName, parentNodeXpath, jsonData,
toOffsetDateTime(observedTimestamp));
return new ResponseEntity<>(HttpStatus.OK);
}
@Override
- public ResponseEntity<Void> deleteListNodeElements(final String dataspaceName, final String anchorName,
- final String listNodeXpath, final String observedTimestamp) {
+ public ResponseEntity<Void> deleteListOrListElement(final String dataspaceName, final String anchorName,
+ final String listElementXpath, final String observedTimestamp) {
cpsDataService
- .deleteListNodeData(dataspaceName, anchorName, listNodeXpath, toOffsetDateTime(observedTimestamp));
+ .deleteListOrListElement(dataspaceName, anchorName, listElementXpath, toOffsetDateTime(observedTimestamp));
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
@@ -114,7 +114,7 @@ public class DataRestController implements CpsDataApi {
return ROOT_XPATH.equals(xpath);
}
- private OffsetDateTime toOffsetDateTime(final String datetTimestamp) {
+ private static OffsetDateTime toOffsetDateTime(final String datetTimestamp) {
try {
return StringUtils.isEmpty(datetTimestamp)
? null : OffsetDateTime.parse(datetTimestamp, ISO_TIMESTAMP_FORMATTER);
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 a54f3bc95d..06f2f5795f 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
@@ -22,14 +22,6 @@
package org.onap.cps.rest.controller
-import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
-import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
-
import org.onap.cps.api.CpsDataService
import org.onap.cps.spi.model.DataNode
import org.onap.cps.spi.model.DataNodeBuilder
@@ -44,6 +36,14 @@ import org.springframework.test.web.servlet.MockMvc
import spock.lang.Shared
import spock.lang.Specification
+import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
+import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
+
@WebMvcTest(DataRestController)
class DataRestControllerSpec extends Specification {
@@ -145,11 +145,11 @@ class DataRestControllerSpec extends Specification {
'without observed-timestamp' | null
}
- def 'Create list node child elements #scenario.'() {
+ def 'Save list elements #scenario.'() {
given: 'parent node xpath and json data inputs'
def parentNodeXpath = 'parent node xpath'
def jsonData = 'json data'
- when: 'post is invoked list-node endpoint'
+ when: 'list-node endpoint is invoked with post (create) operation'
def postRequestBuilder = post("$dataNodeBaseEndpoint/anchors/$anchorName/list-nodes")
.contentType(MediaType.APPLICATION_JSON)
.param('xpath', parentNodeXpath)
@@ -160,7 +160,7 @@ class DataRestControllerSpec extends Specification {
then: 'a created response is returned'
response.status == expectedHttpStatus.value()
then: 'the java API was called with the correct parameters'
- expectedApiCount * mockCpsDataService.saveListNodeData(dataspaceName, anchorName, parentNodeXpath, jsonData,
+ expectedApiCount * mockCpsDataService.saveListElements(dataspaceName, anchorName, parentNodeXpath, jsonData,
{ it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
where:
scenario | observedTimestamp || expectedApiCount | expectedHttpStatus
@@ -303,22 +303,19 @@ class DataRestControllerSpec extends Specification {
'with invalid observed-timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST
}
- def 'Replace list node child elements.'() {
- given: 'parent node xpath and json data inputs'
- def parentNodeXpath = 'parent node xpath'
- def jsonData = 'json data'
- when: 'put is invoked list-node endpoint'
+ def 'Replace list content #scenario.'() {
+ when: 'list-nodes endpoint is invoked with put (update) operation'
def putRequestBuilder = put("$dataNodeBaseEndpoint/anchors/$anchorName/list-nodes")
.contentType(MediaType.APPLICATION_JSON)
- .param('xpath', parentNodeXpath)
- .content(jsonData)
+ .param('xpath', 'parent xpath')
+ .content('json data')
if (observedTimestamp != null)
putRequestBuilder.param('observed-timestamp', observedTimestamp)
def response = mvc.perform(putRequestBuilder).andReturn().response
then: 'a success response is returned'
response.status == expectedHttpStatus.value()
and: 'the java API was called with the correct parameters'
- expectedApiCount * mockCpsDataService.replaceListNodeData(dataspaceName, anchorName, parentNodeXpath, jsonData,
+ expectedApiCount * mockCpsDataService.replaceListContent(dataspaceName, anchorName, 'parent xpath', 'json data',
{ it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
where:
scenario | observedTimestamp || expectedApiCount | expectedHttpStatus
@@ -327,19 +324,17 @@ class DataRestControllerSpec extends Specification {
'with invalid observed-timestamp' | 'invalid' || 0 | HttpStatus.BAD_REQUEST
}
- def 'Delete list node child elements. #scenario'() {
- given: 'list node xpath'
- def listNodeXpath = 'list node xpath'
- when: 'delete is invoked list-node endpoint'
+ def 'Delete list element #scenario.'() {
+ when: 'list-nodes endpoint is invoked with delete operation'
def deleteRequestBuilder = delete("$dataNodeBaseEndpoint/anchors/$anchorName/list-nodes")
- .param('xpath', listNodeXpath)
+ .param('xpath', 'list element xpath')
if (observedTimestamp != null)
deleteRequestBuilder.param('observed-timestamp', observedTimestamp)
def response = mvc.perform(deleteRequestBuilder).andReturn().response
then: 'a success response is returned'
response.status == expectedHttpStatus.value()
and: 'the java API was called with the correct parameters'
- expectedApiCount * mockCpsDataService.deleteListNodeData(dataspaceName, anchorName, listNodeXpath,
+ expectedApiCount * mockCpsDataService.deleteListOrListElement(dataspaceName, anchorName, 'list element xpath',
{ it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
where:
scenario | observedTimestamp || expectedApiCount | expectedHttpStatus
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 2397d31792..8dc6c2f69c 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
@@ -91,7 +91,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 = "\\[(\\@([^/]*?)){0,99}( and)*\\]$";
+ private static final String REG_EX_FOR_LIST_ELEMENT_KEY_PREDICATE = "\\[(\\@([^/]*?)){0,99}( and)*\\]$";
@Override
public void addChildDataNode(final String dataspaceName, final String anchorName, final String parentXpath,
@@ -108,7 +108,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
}
@Override
- public void addListDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath,
+ public void addListElements(final String dataspaceName, final String anchorName, final String parentNodeXpath,
final Collection<DataNode> dataNodes) {
final FragmentEntity parentFragment = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
final List<FragmentEntity> newFragmentEntities =
@@ -296,23 +296,23 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
}
private static void replaceDataNodeTree(final FragmentEntity existingFragmentEntity,
- final DataNode submittedDataNode) {
+ final DataNode newDataNode) {
- existingFragmentEntity.setAttributes(GSON.toJson(submittedDataNode.getLeaves()));
+ existingFragmentEntity.setAttributes(GSON.toJson(newDataNode.getLeaves()));
final Map<String, FragmentEntity> existingChildrenByXpath = existingFragmentEntity.getChildFragments()
.stream().collect(Collectors.toMap(FragmentEntity::getXpath, childFragmentEntity -> childFragmentEntity));
final Collection<FragmentEntity> updatedChildFragments = new HashSet<>();
- for (final DataNode submittedChildDataNode : submittedDataNode.getChildDataNodes()) {
+ for (final DataNode newDataNodeChild : newDataNode.getChildDataNodes()) {
final FragmentEntity childFragment;
- if (isNewDataNode(submittedChildDataNode, existingChildrenByXpath)) {
+ if (isNewDataNode(newDataNodeChild, existingChildrenByXpath)) {
childFragment = convertToFragmentWithAllDescendants(
- existingFragmentEntity.getDataspace(), existingFragmentEntity.getAnchor(), submittedChildDataNode);
+ existingFragmentEntity.getDataspace(), existingFragmentEntity.getAnchor(), newDataNodeChild);
} else {
- childFragment = existingChildrenByXpath.get(submittedChildDataNode.getXpath());
- replaceDataNodeTree(childFragment, submittedChildDataNode);
+ childFragment = existingChildrenByXpath.get(newDataNodeChild.getXpath());
+ replaceDataNodeTree(childFragment, newDataNodeChild);
}
updatedChildFragments.add(childFragment);
}
@@ -322,19 +322,19 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
@Override
@Transactional
- public void replaceListDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath,
- final Collection<DataNode> replacementDataNodes) {
+ public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
+ final Collection<DataNode> newListElements) {
final FragmentEntity parentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
- final String listNodeXpathPrefix = getListNodeXpathPrefix(replacementDataNodes);
+ final String listElementXpathPrefix = getListElementXpathPrefix(newListElements);
final Map<String, FragmentEntity> existingListElementFragmentEntitiesByXPath =
- extractListElementFragmentEntitiesByXPath(parentEntity.getChildFragments(), listNodeXpathPrefix);
- removeExistingListElements(parentEntity.getChildFragments(), existingListElementFragmentEntitiesByXPath);
+ extractListElementFragmentEntitiesByXPath(parentEntity.getChildFragments(), listElementXpathPrefix);
+ deleteListElements(parentEntity.getChildFragments(), existingListElementFragmentEntitiesByXPath);
final Set<FragmentEntity> updatedChildFragmentEntities = new HashSet<>();
- for (final DataNode replacementDataNode : replacementDataNodes) {
- final FragmentEntity existingListNodeElementEntity =
- existingListElementFragmentEntitiesByXPath.get(replacementDataNode.getXpath());
- final FragmentEntity entityToBeAdded = getFragmentForReplacement(parentEntity, replacementDataNode,
- existingListNodeElementEntity);
+ for (final DataNode newListElement : newListElements) {
+ final FragmentEntity existingListElementEntity =
+ existingListElementFragmentEntitiesByXPath.get(newListElement.getXpath());
+ final FragmentEntity entityToBeAdded = getFragmentForReplacement(parentEntity, newListElement,
+ existingListElementEntity);
updatedChildFragmentEntities.add(entityToBeAdded);
}
@@ -342,85 +342,111 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
fragmentRepository.save(parentEntity);
}
- private static void removeExistingListElements(
+
+ @Override
+ @Transactional
+ public void deleteListDataNode(final String dataspaceName, final String anchorName,
+ final String targetXpath) {
+ deleteDataNode(dataspaceName, anchorName, targetXpath, true);
+ }
+
+ @Override
+ @Transactional
+ public void deleteDataNode(final String dataspaceName, final String anchorName, final String targetXpath) {
+ deleteDataNode(dataspaceName, anchorName, targetXpath, false);
+ }
+
+ private void deleteDataNode(final String dataspaceName, final String anchorName, final String targetXpath,
+ final boolean onlySupportListNodeDeletion) {
+ final String parentNodeXpath = targetXpath.substring(0, targetXpath.lastIndexOf('/'));
+ final FragmentEntity parentFragmentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
+ final String lastXpathElement = targetXpath.substring(targetXpath.lastIndexOf('/'));
+ final boolean isListElement = Pattern.compile(REG_EX_FOR_LIST_ELEMENT_KEY_PREDICATE)
+ .matcher(lastXpathElement).find();
+ boolean targetExist;
+ if (isListElement) {
+ targetExist = deleteDataNode(parentFragmentEntity, targetXpath);
+ } else {
+ targetExist = deleteAllListElements(parentFragmentEntity, targetXpath);
+ final boolean tryToDeleteDataNode = !targetExist && !onlySupportListNodeDeletion;
+ if (tryToDeleteDataNode) {
+ targetExist = deleteDataNode(parentFragmentEntity, targetXpath);
+ }
+ }
+ if (!targetExist) {
+ final String additionalInformation = onlySupportListNodeDeletion
+ ? "The target is probably not a List." : "";
+ throw new DataNodeNotFoundException(parentFragmentEntity.getDataspace().getName(),
+ parentFragmentEntity.getAnchor().getName(), targetXpath, additionalInformation);
+ }
+ }
+
+ private boolean deleteDataNode(final FragmentEntity parentFragmentEntity, final String targetXpath) {
+ if (parentFragmentEntity.getChildFragments()
+ .removeIf(fragment -> fragment.getXpath().equals(targetXpath))) {
+ fragmentRepository.save(parentFragmentEntity);
+ return true;
+ }
+ return false;
+ }
+
+
+ private boolean deleteAllListElements(final FragmentEntity parentFragmentEntity, final String listXpath) {
+ final String deleteTargetXpathPrefix = listXpath + "[";
+ if (parentFragmentEntity.getChildFragments()
+ .removeIf(fragment -> fragment.getXpath().startsWith(deleteTargetXpathPrefix))) {
+ fragmentRepository.save(parentFragmentEntity);
+ return true;
+ }
+ return false;
+ }
+
+ private static void deleteListElements(
final Collection<FragmentEntity> fragmentEntities,
final Map<String, FragmentEntity> existingListElementFragmentEntitiesByXPath) {
fragmentEntities.removeAll(existingListElementFragmentEntitiesByXPath.values());
}
- private static String getListNodeXpathPrefix(final Collection<DataNode> replacementDataNodes) {
- final String firstChildNodeXpath = replacementDataNodes.iterator().next().getXpath();
+ private static String getListElementXpathPrefix(final Collection<DataNode> newListElements) {
+ final String firstChildNodeXpath = newListElements.iterator().next().getXpath();
return firstChildNodeXpath.substring(0, firstChildNodeXpath.lastIndexOf("[") + 1);
}
private static FragmentEntity getFragmentForReplacement(final FragmentEntity parentEntity,
- final DataNode replacementDataNode,
- final FragmentEntity existingListNodeElementEntity) {
- if (existingListNodeElementEntity == null) {
+ final DataNode newListElement,
+ final FragmentEntity existingListElementEntity) {
+ if (existingListElementEntity == null) {
return convertToFragmentWithAllDescendants(
- parentEntity.getDataspace(), parentEntity.getAnchor(), replacementDataNode);
+ parentEntity.getDataspace(), parentEntity.getAnchor(), newListElement);
}
- if (replacementDataNode.getChildDataNodes().isEmpty()) {
- copyAttributesFromReplacementDataNode(existingListNodeElementEntity, replacementDataNode);
- existingListNodeElementEntity.getChildFragments().clear();
+ if (newListElement.getChildDataNodes().isEmpty()) {
+ copyAttributesFromNewListElement(existingListElementEntity, newListElement);
+ existingListElementEntity.getChildFragments().clear();
} else {
- replaceDataNodeTree(existingListNodeElementEntity, replacementDataNode);
+ replaceDataNodeTree(existingListElementEntity, newListElement);
}
- return existingListNodeElementEntity;
+ return existingListElementEntity;
}
private static boolean isNewDataNode(final DataNode replacementDataNode,
- final Map<String, FragmentEntity> existingListNodeElementsByXpath) {
- return !existingListNodeElementsByXpath.containsKey(replacementDataNode.getXpath());
+ final Map<String, FragmentEntity> existingListElementsByXpath) {
+ return !existingListElementsByXpath.containsKey(replacementDataNode.getXpath());
}
- private static void copyAttributesFromReplacementDataNode(final FragmentEntity existingListNodeElementEntity,
- final DataNode replacementDataNode) {
+ private static void copyAttributesFromNewListElement(final FragmentEntity existingListElementEntity,
+ final DataNode newListElement) {
final FragmentEntity replacementFragmentEntity =
- FragmentEntity.builder().attributes(GSON.toJson(replacementDataNode.getLeaves())).build();
- existingListNodeElementEntity.setAttributes(replacementFragmentEntity.getAttributes());
+ FragmentEntity.builder().attributes(GSON.toJson(newListElement.getLeaves())).build();
+ existingListElementEntity.setAttributes(replacementFragmentEntity.getAttributes());
}
private static Map<String, FragmentEntity> extractListElementFragmentEntitiesByXPath(
- final Set<FragmentEntity> childEntities, final String listNodeXpathPrefix) {
+ final Set<FragmentEntity> childEntities, final String listElementXpathPrefix) {
return childEntities.stream()
- .filter(fragmentEntity -> fragmentEntity.getXpath().startsWith(listNodeXpathPrefix))
+ .filter(fragmentEntity -> fragmentEntity.getXpath().startsWith(listElementXpathPrefix))
.collect(Collectors.toMap(FragmentEntity::getXpath, fragmentEntity -> fragmentEntity));
}
- @Override
- @Transactional
- public void deleteListDataNodes(final String dataspaceName, final String anchorName, final String listNodeXpath) {
- final String parentNodeXpath = listNodeXpath.substring(0, listNodeXpath.lastIndexOf('/'));
- final FragmentEntity parentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
- final String 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 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);
- }
- }
-
private static boolean isRootXpath(final String xpath) {
return "/".equals(xpath) || "".equals(xpath);
}
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 144b18b535..85e1155cff 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
@@ -32,7 +32,6 @@ import org.onap.cps.spi.exceptions.DataNodeNotFoundException
import org.onap.cps.spi.exceptions.DataspaceNotFoundException
import org.onap.cps.spi.model.DataNode
import org.onap.cps.spi.model.DataNodeBuilder
-import org.spockframework.util.CollectionUtil
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.context.jdbc.Sql
@@ -157,20 +156,20 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
}
@Sql([CLEAR_DATA, SET_DATA])
- def 'Add list-node fragment with multiple elements including an element with a child datanode.'() {
+ def 'Add multiple list elements including an element with a child datanode.'() {
given: 'two new data nodes for an existing list'
- def listNodeXpaths = ['/parent-201/child-204[@key="B"]', '/parent-201/child-204[@key="C"]']
- def listNodeCollection = buildDataNodeCollection(listNodeXpaths)
+ def listElementXpaths = ['/parent-201/child-204[@key="B"]', '/parent-201/child-204[@key="C"]']
+ def listElements = toDataNodes(listElementXpaths)
and: 'a child node for one of the new data nodes'
def childDataNode = buildDataNode('/parent-201/child-204[@key="C"]/grand-child-204[@key2="Z"]', [leave:'value'], [])
- listNodeCollection.iterator().next().childDataNodes = [childDataNode]
+ listElements[0].childDataNodes = [childDataNode]
when: 'the data nodes (list elements) are added to existing parent node'
- objectUnderTest.addListDataNodes(DATASPACE_NAME, ANCHOR_NAME3, '/parent-201', listNodeCollection)
+ objectUnderTest.addListElements(DATASPACE_NAME, ANCHOR_NAME3, '/parent-201', listElements)
then: 'new entries successfully persisted, parent node now contains 5 children (2 new + 3 existing before)'
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)
+ assert allChildXpaths.containsAll(listElementXpaths)
and: 'the child node of the new list entry is also present'
def dataspaceEntity = dataspaceRepository.getByName(DATASPACE_NAME)
def anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, ANCHOR_NAME3)
@@ -179,15 +178,15 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
}
@Sql([CLEAR_DATA, SET_DATA])
- def 'Add 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 added to existing parent node'
- objectUnderTest.addListDataNodes(DATASPACE_NAME, ANCHOR_NAME3, parentNodeXpath, listNodeCollection)
+ def 'Add list element error scenario: #scenario.'() {
+ given: 'list element as a collection of data nodes'
+ def listElementCollection = toDataNodes(listElementXpaths)
+ when: 'attempt to add list elements to parent node'
+ objectUnderTest.addListElements(DATASPACE_NAME, ANCHOR_NAME3, parentNodeXpath, listElementCollection)
then: 'a #expectedException is thrown'
thrown(expectedException)
where: 'following parameters were used'
- scenario | parentNodeXpath | listNodeXpaths || expectedException
+ scenario | parentNodeXpath | listElementXpaths || expectedException
'parent node does not exist' | '/unknown' | ['irrelevant'] || DataNodeNotFoundException
'already existing fragment' | '/parent-201' | ['/parent-201/child-204[@key="A"]'] || AlreadyDefinedException
@@ -385,40 +384,40 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
}
@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, parentXpath, listNodeCollection)
- then: 'child list elements are updated as expected, non-list element remains as is'
- def parentFragment = fragmentRepository.getById(listNodeFragmentID)
+ def 'Replace list content of #scenario.'() {
+ given: 'list element as a collection of data nodes'
+ def listElementCollection = toDataNodes(listElementXpaths)
+ when: 'list elements are replaced within the existing parent node'
+ objectUnderTest.replaceListContent(DATASPACE_NAME, ANCHOR_NAME3, parentXpath, listElementCollection)
+ then: 'list elements are updated as expected, non-list element remains as is'
+ def parentFragment = fragmentRepository.getById(listElementFragmentID)
def allChildXpaths = parentFragment.getChildFragments().collect { it.getXpath() }
assert allChildXpaths.size() == expectedChildXpaths.size()
assert allChildXpaths.containsAll(expectedChildXpaths)
where: 'following parameters were used'
- scenario | listNodeXpaths |parentXpath |listNodeFragmentID || expectedChildXpaths
- 'existing list node with non existing key' | ['/parent-201/child-204[@key="B"]'] | '/parent-201' | LIST_DATA_NODE_PARENT201_FRAGMENT_ID || ['/parent-201/child-203', '/parent-201/child-204[@key="B"]']
- 'non existing list node with non existing key' | ['/parent-201/child-205[@key="1"]'] | '/parent-201' | LIST_DATA_NODE_PARENT201_FRAGMENT_ID || ['/parent-201/child-203', '/parent-201/child-204[@key="A"]', '/parent-201/child-204[@key="X"]', '/parent-201/child-205[@key="1"]']
- 'existing list node with 1 existing key' | ['/parent-201/child-204[@key="X"]'] | '/parent-201' | LIST_DATA_NODE_PARENT201_FRAGMENT_ID || ['/parent-201/child-203', '/parent-201/child-204[@key="X"]']
- 'existing list-node with combined keys' | ['/parent-202/child-205[@key="A"]'] | '/parent-202' | LIST_DATA_NODE_PARENT202_FRAGMENT_ID || ['/parent-202/child-206[@key="A"]', '/parent-202/child-205[@key="A"]']
- 'existing grandchild list-node' | ['/parent-200/child-202/grand-child-202[@key="E"]'] | '/parent-200/child-202' | LIST_DATA_NODE_CHILD202_FRAGMENT_ID || ['/parent-200/child-202/grand-child-202[@key="E"]']
- 'existing list node with two list nodes' | ['/parent-201/child-204[@key="new X"]', '/parent-201/child-204[@key="Y"]'] | '/parent-201' | LIST_DATA_NODE_PARENT201_FRAGMENT_ID || ['/parent-201/child-203', '/parent-201/child-204[@key="new X"]', '/parent-201/child-204[@key="Y"]']
- 'existing list node with compounded list node' | ['/parent-202/child-205[@key="A" and @key2="B"]'] | '/parent-202' | LIST_DATA_NODE_PARENT202_FRAGMENT_ID || ['/parent-202/child-206[@key="A"]', '/parent-202/child-205[@key="A" and @key2="B"]']
- 'existing list node with list node with parent with key value' | ['/parent-204[@key="L"]/child-210[@key="N"]'] | '/parent-204[@key="L"]' | LIST_DATA_NODE_PARENT204_FRAGMENT_ID || ['/parent-204[@key="L"]/child-210[@key="N"]']
+ scenario | listElementXpaths | parentXpath | listElementFragmentID || expectedChildXpaths
+ 'existing list element with non existing key' | ['/parent-201/child-204[@key="B"]'] | '/parent-201' | LIST_DATA_NODE_PARENT201_FRAGMENT_ID || ['/parent-201/child-203', '/parent-201/child-204[@key="B"]']
+ 'non existing list element with non existing key' | ['/parent-201/child-205[@key="1"]'] | '/parent-201' | LIST_DATA_NODE_PARENT201_FRAGMENT_ID || ['/parent-201/child-203', '/parent-201/child-204[@key="A"]', '/parent-201/child-204[@key="X"]', '/parent-201/child-205[@key="1"]']
+ 'list element with 1 existing key' | ['/parent-201/child-204[@key="X"]'] | '/parent-201' | LIST_DATA_NODE_PARENT201_FRAGMENT_ID || ['/parent-201/child-203', '/parent-201/child-204[@key="X"]']
+ 'list element with combined keys' | ['/parent-202/child-205[@key="A"]'] | '/parent-202' | LIST_DATA_NODE_PARENT202_FRAGMENT_ID || ['/parent-202/child-206[@key="A"]', '/parent-202/child-205[@key="A"]']
+ 'grandchild list element' | ['/parent-200/child-202/grand-child-202[@key="E"]'] | '/parent-200/child-202' | LIST_DATA_NODE_CHILD202_FRAGMENT_ID || ['/parent-200/child-202/grand-child-202[@key="E"]']
+ 'list element with two list elements' | ['/parent-201/child-204[@key="new X"]', '/parent-201/child-204[@key="Y"]'] | '/parent-201' | LIST_DATA_NODE_PARENT201_FRAGMENT_ID || ['/parent-201/child-203', '/parent-201/child-204[@key="new X"]', '/parent-201/child-204[@key="Y"]']
+ 'list element with compounded list element' | ['/parent-202/child-205[@key="A" and @key2="B"]'] | '/parent-202' | LIST_DATA_NODE_PARENT202_FRAGMENT_ID || ['/parent-202/child-206[@key="A"]', '/parent-202/child-205[@key="A" and @key2="B"]']
+ 'list element with list element with parent with key value' | ['/parent-204[@key="L"]/child-210[@key="N"]'] | '/parent-204[@key="L"]' | LIST_DATA_NODE_PARENT204_FRAGMENT_ID || ['/parent-204[@key="L"]/child-210[@key="N"]']
}
@Sql([CLEAR_DATA, SET_DATA])
- def 'Replace list-node that has children with #scenario'() {
- given: 'list node data fragment with child data node fragments'
- def grandChildDataNodes = buildDataNodeCollection(grandChildXpaths)
- def listNode = new DataNodeBuilder().withXpath(childXpath).withChildDataNodes(grandChildDataNodes).build()
- when: 'list-node elements replaced within the existing parent node'
- objectUnderTest.replaceListDataNodes(DATASPACE_NAME, ANCHOR_NAME3, parentXpath, [ listNode ])
- then: 'child list elements are updated as expected with non-list elements remaining as is'
- def parentFragment = fragmentRepository.getById(listNodeFragmentId)
+ def 'Replace list content that has #scenario'() {
+ given: 'list element with child list element as a collection of data nodes'
+ def grandChildDataNodes = toDataNodes(grandChildXpaths)
+ def listElementCollection = new DataNodeBuilder().withXpath(childXpath).withChildDataNodes(grandChildDataNodes).build()
+ when: 'list elements replaced within the existing parent node'
+ objectUnderTest.replaceListContent(DATASPACE_NAME, ANCHOR_NAME3, parentXpath, [listElementCollection ])
+ then: 'list elements are updated as expected with non-list elements remaining as is'
+ def parentFragment = fragmentRepository.getById(listElementFragmentId)
def allChildXpaths = parentFragment.getChildFragments().collect { it.getXpath() }
- assert allChildXpaths.size() == expectedChildXpaths.size()
- assert allChildXpaths.containsAll(expectedChildXpaths)
+ assert allChildXpaths.size() == expectedRemainingChildXpaths.size()
+ assert allChildXpaths.containsAll(expectedRemainingChildXpaths)
and: 'grandchild list elements are updated as expected'
def allGrandChildXpaths = parentFragment.getChildFragments().collect(){
it.getChildFragments().collect(){
@@ -429,21 +428,20 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
assert grandChildXpathsToList.size() == expectedGrandChildXpaths.size()
assert grandChildXpathsToList.containsAll(expectedGrandChildXpaths)
where: 'the following parameters are used'
- scenario | parentXpath | childXpath | grandChildXpaths | expectedChildXpaths | listNodeFragmentId
- 'existing grandchild of list node' | '/parent-203' | '/parent-203/child-204[@key="X"]' | ['/parent-203/child-204/grandchild[@key="2"]'] | ['/parent-203/child-203', '/parent-203/child-204[@key="X"]'] | LIST_DATA_NODE_PARENT203_FRAGMENT_ID
- 'existing grandchild of list node with two new nodes' | '/parent-203' | '/parent-203/child-204[@key="X"]' | ['/parent-203/child-204/grandchild[@key="2"]' , '/parent-203/child-204/grandchild[@key="3"]'] | ['/parent-203/child-203', '/parent-203/child-204[@key="X"]'] | LIST_DATA_NODE_PARENT203_FRAGMENT_ID
- 'existing grandchild with compound list node' | '/parent-205' | '/parent-205/child-205[@key="X"]' | ['/parent-205/child-205/grand-child-206[@key="Y" and @key2="Z"]'] | ['/parent-205/child-205', '/parent-205/child-205[@key="X"]'] | LIST_DATA_NODE_PARENT205_FRAGMENT_ID
- 'two existing list node with a new node' | '/parent-205' | '/parent-205/child-205[@key="X"]' | ['/parent-205/child-205/grandchild[@key="A"]'] | ['/parent-205/child-205', '/parent-205/child-205[@key="X"]'] | LIST_DATA_NODE_PARENT205_FRAGMENT_ID
+ scenario | parentXpath | childXpath | grandChildXpaths | listElementFragmentId || expectedRemainingChildXpaths
+ 'grandchild of list' | '/parent-203' | '/parent-203/child-204[@key="X"]' | ['/parent-203/child-204/grandchild[@key="2"]'] | LIST_DATA_NODE_PARENT203_FRAGMENT_ID || ['/parent-203/child-203', '/parent-203/child-204[@key="X"]']
+ 'grandchild of list with two new element' | '/parent-203' | '/parent-203/child-204[@key="X"]' | ['/parent-203/child-204/grandchild[@key="2"]' , '/parent-203/child-204/grandchild[@key="3"]'] | LIST_DATA_NODE_PARENT203_FRAGMENT_ID || ['/parent-203/child-203', '/parent-203/child-204[@key="X"]']
+ 'grandchild with compound list elements' | '/parent-205' | '/parent-205/child-205[@key="X"]' | ['/parent-205/child-205/grand-child-206[@key="Y" and @key2="Z"]'] | LIST_DATA_NODE_PARENT205_FRAGMENT_ID || ['/parent-205/child-205', '/parent-205/child-205[@key="X"]']
}
@Sql([CLEAR_DATA, SET_DATA])
- def 'Replace list-node content of #scenario with grandchildren.'() {
- 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, parentXpath, listNodeCollection)
+ def 'Replace list content of #scenario with grandchildren.'() {
+ given: 'list element as a collection of data nodes'
+ def listElementCollection = toDataNodes(listElementXpaths)
+ when: 'list elements are replaced within the existing parent node'
+ objectUnderTest.replaceListContent(DATASPACE_NAME, ANCHOR_NAME3, parentXpath, listElementCollection)
then: 'child list elements are updated as expected with non-list elements remaining as is'
- def parentFragment = fragmentRepository.getById(listNodeFragmentID)
+ def parentFragment = fragmentRepository.getById(listElementFragmentID)
def allChildXpaths = parentFragment.getChildFragments().collect { it.getXpath() }
assert allChildXpaths.size() == expectedChildXpaths.size()
assert allChildXpaths.containsAll(expectedChildXpaths)
@@ -455,67 +453,94 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
assert allGrandChildXpaths.size() == expectedGrandChildXpaths.size()
assert allGrandChildXpaths.containsAll(expectedGrandChildXpaths)
where: 'following parameters were used'
- scenario | listNodeXpaths | parentXpath | listNodeFragmentID || expectedChildXpaths | expectedGrandChildXpaths
- 'existing list node with existing keys' | ['/parent-203/child-204[@key="X"]'] | '/parent-203' | LIST_DATA_NODE_PARENT203_FRAGMENT_ID || ['/parent-203/child-203', '/parent-203/child-204[@key="X"]'] | []
- 'non existing list node with existing keys' | ['/parent-203/child-204[@key="V"]'] | '/parent-203' | LIST_DATA_NODE_PARENT203_FRAGMENT_ID || ['/parent-203/child-203', '/parent-203/child-204[@key="V"]'] | []
+ scenario | listElementXpaths | parentXpath | listElementFragmentID || expectedChildXpaths | expectedGrandChildXpaths
+ 'existing list element with existing keys' | ['/parent-203/child-204[@key="X"]'] | '/parent-203' | LIST_DATA_NODE_PARENT203_FRAGMENT_ID || ['/parent-203/child-203', '/parent-203/child-204[@key="X"]'] | []
+ 'non existing list element with existing keys' | ['/parent-203/child-204[@key="V"]'] | '/parent-203' | LIST_DATA_NODE_PARENT203_FRAGMENT_ID || ['/parent-203/child-203', '/parent-203/child-204[@key="V"]'] | []
}
@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)
+ def 'Replace content error scenario: #scenario.'() {
+ given: 'list element as a collection of data nodes'
+ def listElementCollection = toDataNodes(listElementXpaths)
+ when: 'list elements were replaced under existing parent node'
+ objectUnderTest.replaceListContent(DATASPACE_NAME, ANCHOR_NAME3, parentNodeXpath, listElementCollection)
then: 'a #expectedException is thrown'
thrown(expectedException)
where: 'following parameters were used'
- scenario | parentNodeXpath | listNodeXpaths || expectedException
+ scenario | parentNodeXpath | listElementXpaths || expectedException
'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)
+ def 'Delete list scenario: #scenario.'() {
+ when: 'deleting list is executed for: #scenario.'
+ objectUnderTest.deleteListDataNode(DATASPACE_NAME, ANCHOR_NAME3, targetXpaths)
+ then: 'only the expected children remain'
+ def parentFragment = fragmentRepository.getById(parentFragmentId)
+ def remainingChildXpaths = parentFragment.getChildFragments().collect { it.getXpath() }
+ assert remainingChildXpaths.size() == expectedRemainingChildXpaths.size()
+ assert remainingChildXpaths.containsAll(expectedRemainingChildXpaths)
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 || []
+ scenario | targetXpaths | parentFragmentId || expectedRemainingChildXpaths
+ 'list element with key' | '/parent-203/child-204[@key="A"]' | LIST_DATA_NODE_PARENT203_FRAGMENT_ID || ['/parent-203/child-203', '/parent-203/child-204[@key="X"]']
+ 'list element with combined keys' | '/parent-202/child-205[@key="A" and @key2="B"]' | LIST_DATA_NODE_PARENT202_FRAGMENT_ID || ['/parent-202/child-206[@key="A"]']
+ 'whole list' | '/parent-203/child-204' | LIST_DATA_NODE_PARENT203_FRAGMENT_ID || ['/parent-203/child-203']
+ 'list element under list element' | '/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"]']
}
@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)
+ def 'Delete list error scenario: #scenario.'() {
+ when: 'attempting to delete scenario: #scenario.'
+ objectUnderTest.deleteListDataNode(DATASPACE_NAME, ANCHOR_NAME3, targetXpaths)
+ then: 'a DataNodeNotFoundException is thrown'
+ thrown(DataNodeNotFoundException)
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
+ scenario | targetXpaths
+ 'whole list, parent node does not exist' | '/unknown/some-child'
+ 'list element, parent node does not exist' | '/unknown/child-204[@key="A"]'
+ 'whole list does not exist' | '/parent-200/unknown'
+ 'list element, list does not exist' | '/parent-200/unknown[@key="C"]'
+ 'list element, element does not exist' | '/parent-203/child-204[@key="C"]'
+ 'valid datanode but not a list' | '/parent-200/child-202'
+ }
+
+ @Sql([CLEAR_DATA, SET_DATA])
+ def 'Confirm deletion of #scenario.'() {
+ given: 'a valid data node'
+ def dataNode
+ def dataNodeXpath
+ when: 'data nodes are deleted'
+ objectUnderTest.deleteDataNode(DATASPACE_NAME, ANCHOR_NAME3, xpathForDeletion)
+ then: 'verify data nodes are removed'
+ try {
+ dataNode = objectUnderTest.getDataNode(DATASPACE_NAME, ANCHOR_NAME3, getDataNodesXpaths, INCLUDE_ALL_DESCENDANTS)
+ dataNodeXpath = dataNode.getXpath()
+ assert dataNodeXpath == expectedXpaths
+ } catch (DataNodeNotFoundException) {
+ assert dataNodeXpath == expectedXpaths
+ }
+ where: 'following parameters were used'
+ scenario | xpathForDeletion | getDataNodesXpaths || expectedXpaths
+ 'child of target' | '/parent-206/child-206' | '/parent-206/child-206' || null
+ 'child data node, parent still exists' | '/parent-206/child-206' | '/parent-206' || '/parent-206'
+ 'list element' | '/parent-206/child-206/grand-child-206[@key="A"]' | '/parent-206/child-206/grand-child-206[@key="A"]' || null
+ 'list element, sibling still exists' | '/parent-206/child-206/grand-child-206[@key="A"]' | '/parent-206/child-206/grand-child-206[@key="X"]' || '/parent-206/child-206/grand-child-206[@key="X"]'
+ }
+ @Sql([CLEAR_DATA, SET_DATA])
+ def 'Delete data node with #scenario.'() {
+ when: 'data node is deleted'
+ objectUnderTest.deleteDataNode(DATASPACE_NAME, ANCHOR_NAME3, datanodeXpath)
+ then: 'a #expectedException is thrown'
+ thrown(DataNodeNotFoundException)
+ where: 'the following parameters were used'
+ scenario | datanodeXpath
+ 'valid data node, non existent child node' | '/parent-203/child-non-existent'
+ 'invalid list element' | '/parent-206/child-206/grand-child-206@key="A"]'
}
- static Collection<DataNode> buildDataNodeCollection(xpaths) {
+ static Collection<DataNode> toDataNodes(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 f6e887e017..5d3a328327 100755
--- a/cps-ri/src/test/resources/data/fragment.sql
+++ b/cps-ri/src/test/resources/data/fragment.sql
@@ -68,4 +68,9 @@ INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH, ATTRIBUTES)
(4222, 1001, 3003, 4221, '/parent-205/child-205', '{}'),
(4223, 1001, 3003, 4221, '/parent-205/child-205[@key="X"]', '{"key": "X"}'),
(4224, 1001, 3003, 4223, '/parent-205/child-205[@key="X"]/grand-child-206[@key="Y"]', '{"key": "Y", "key2": "Z"}'),
- (4225, 1001, 3003, 4223, '/parent-205/child-205[@key="X"]/grand-child-206[@key="Y" and @key2="Z"]', '{"key": "Y", "key2": "Z"}'); \ No newline at end of file
+ (4225, 1001, 3003, 4223, '/parent-205/child-205[@key="X"]/grand-child-206[@key="Y" and @key2="Z"]', '{"key": "Y", "key2": "Z"}'),
+ (4226, 1001, 3003, null, '/parent-206', '{"leaf-value": "original"}'),
+ (4227, 1001, 3003, 4226, '/parent-206/child-206', '{}'),
+ (4228, 1001, 3003, 4227, '/parent-206/child-206/grand-child-206', '{}'),
+ (4229, 1001, 3003, 4227, '/parent-206/child-206/grand-child-206[@key="A"]', '{"key": "A"}'),
+ (4230, 1001, 3003, 4227, '/parent-206/child-206/grand-child-206[@key="X"]', '{"key": "X"}'); \ 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 31a7517340..e6cb65fa78 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
@@ -56,17 +56,18 @@ public interface CpsDataService {
@NonNull String jsonData, OffsetDateTime observedTimestamp);
/**
- * Persists child data fragment representing list-node (with one or more elements) under existing data node for the
+ * Persists child data fragment representing one or more list 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
+ * @param dataspaceName dataspace name
+ * @param anchorName anchor name
+ * @param parentNodeXpath parent node xpath
+ * @param jsonData json data representing list element(s)
* @param observedTimestamp observedTimestamp
*/
- void saveListNodeData(@NonNull String dataspaceName, @NonNull String anchorName, @NonNull String parentNodeXpath,
- @NonNull String jsonData, OffsetDateTime observedTimestamp);
+ void saveListElements(@NonNull String dataspaceName, @NonNull String anchorName,
+ @NonNull String parentNodeXpath,
+ @NonNull String jsonData, OffsetDateTime observedTimestamp);
/**
* Retrieves datanode by XPath for given dataspace and anchor.
@@ -106,29 +107,28 @@ public interface CpsDataService {
@NonNull String jsonData, OffsetDateTime observedTimestamp);
/**
- * Replaces (if exists) child data fragment representing list-node (with one or more elements) under existing data
- * node for the given anchor and dataspace.
+ * Replaces list content by removing all existing elements and inserting the given new elements as json
+ * under given parent, anchor and dataspace.
*
* @param dataspaceName dataspace name
* @param anchorName anchor name
* @param parentNodeXpath parent node xpath
- * @param jsonData json data representing list element
+ * @param jsonData json data representing the new list elements
* @param observedTimestamp observedTimestamp
*/
- void replaceListNodeData(@NonNull String dataspaceName, @NonNull String anchorName, @NonNull String parentNodeXpath,
- @NonNull String jsonData, OffsetDateTime observedTimestamp);
+ void replaceListContent(@NonNull String dataspaceName, @NonNull String anchorName, @NonNull String parentNodeXpath,
+ @NonNull String jsonData, OffsetDateTime observedTimestamp);
/**
- * Deletes (if exists) child data fragment representing list-node (with one or more elements) under existing data
- * node for the given anchor and dataspace.
+ * Deletes a list or a list-element under given anchor and dataspace.
*
* @param dataspaceName dataspace name
* @param anchorName anchor name
- * @param listNodeXpath list node xpath
+ * @param listElementXpath list element xpath
* @param observedTimestamp observedTimestamp
*/
- void deleteListNodeData(@NonNull String dataspaceName, @NonNull String anchorName, @NonNull String listNodeXpath,
- OffsetDateTime observedTimestamp);
+ void deleteListOrListElement(@NonNull String dataspaceName, @NonNull String anchorName,
+ @NonNull String listElementXpath, OffsetDateTime observedTimestamp);
/**
* Updates leaves of DataNode for given dataspace and anchor using xpath, along with the leaves of each Child Data
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 7b3567ed3c..44a17f89dd 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
@@ -64,7 +64,7 @@ public class CpsDataServiceImpl implements CpsDataService {
@Override
public void saveData(final String dataspaceName, final String anchorName, final String jsonData,
final OffsetDateTime observedTimestamp) {
- final var dataNode = buildDataNodeFromJson(dataspaceName, anchorName, ROOT_NODE_XPATH, jsonData);
+ final var dataNode = buildDataNode(dataspaceName, anchorName, ROOT_NODE_XPATH, jsonData);
cpsDataPersistenceService.storeDataNode(dataspaceName, anchorName, dataNode);
processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp);
}
@@ -72,17 +72,18 @@ public class CpsDataServiceImpl implements CpsDataService {
@Override
public void saveData(final String dataspaceName, final String anchorName, final String parentNodeXpath,
final String jsonData, final OffsetDateTime observedTimestamp) {
- final var dataNode = buildDataNodeFromJson(dataspaceName, anchorName, parentNodeXpath, jsonData);
+ final var dataNode = buildDataNode(dataspaceName, anchorName, parentNodeXpath, jsonData);
cpsDataPersistenceService.addChildDataNode(dataspaceName, anchorName, parentNodeXpath, dataNode);
processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp);
}
@Override
- public void saveListNodeData(final String dataspaceName, final String anchorName,
+ public void saveListElements(final String dataspaceName, final String anchorName,
final String parentNodeXpath, final String jsonData, final OffsetDateTime observedTimestamp) {
- final Collection<DataNode> dataNodesCollection =
- buildDataNodeCollectionFromJson(dataspaceName, anchorName, parentNodeXpath, jsonData);
- cpsDataPersistenceService.addListDataNodes(dataspaceName, anchorName, parentNodeXpath, dataNodesCollection);
+ final Collection<DataNode> listElementDataNodeCollection =
+ buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonData);
+ cpsDataPersistenceService.addListElements(dataspaceName, anchorName, parentNodeXpath,
+ listElementDataNodeCollection);
processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp);
}
@@ -95,7 +96,7 @@ public class CpsDataServiceImpl implements CpsDataService {
@Override
public void updateNodeLeaves(final String dataspaceName, final String anchorName, final String parentNodeXpath,
final String jsonData, final OffsetDateTime observedTimestamp) {
- final var dataNode = buildDataNodeFromJson(dataspaceName, anchorName, parentNodeXpath, jsonData);
+ final var dataNode = buildDataNode(dataspaceName, anchorName, parentNodeXpath, jsonData);
cpsDataPersistenceService
.updateDataLeaves(dataspaceName, anchorName, dataNode.getXpath(), dataNode.getLeaves());
processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp);
@@ -107,7 +108,8 @@ public class CpsDataServiceImpl implements CpsDataService {
final String dataNodeUpdatesAsJson,
final OffsetDateTime observedTimestamp) {
final Collection<DataNode> dataNodeUpdates =
- buildDataNodeCollectionFromJson(dataspaceName, anchorName, parentNodeXpath, dataNodeUpdatesAsJson);
+ buildDataNodes(dataspaceName, anchorName,
+ parentNodeXpath, dataNodeUpdatesAsJson);
for (final DataNode dataNodeUpdate : dataNodeUpdates) {
processDataNodeUpdate(dataspaceName, anchorName, dataNodeUpdate);
}
@@ -117,30 +119,29 @@ public class CpsDataServiceImpl implements CpsDataService {
@Override
public void replaceNodeTree(final String dataspaceName, final String anchorName, final String parentNodeXpath,
final String jsonData, final OffsetDateTime observedTimestamp) {
- final var dataNode = buildDataNodeFromJson(dataspaceName, anchorName, parentNodeXpath, jsonData);
+ final var dataNode = buildDataNode(dataspaceName, anchorName, parentNodeXpath, jsonData);
cpsDataPersistenceService.replaceDataNodeTree(dataspaceName, anchorName, dataNode);
processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp);
}
@Override
- public void replaceListNodeData(final String dataspaceName, final String anchorName, final String parentNodeXpath,
- final String jsonData, final OffsetDateTime observedTimestamp) {
- final Collection<DataNode> dataNodes =
- buildDataNodeCollectionFromJson(dataspaceName, anchorName, parentNodeXpath, jsonData);
- cpsDataPersistenceService.replaceListDataNodes(dataspaceName, anchorName, parentNodeXpath, dataNodes);
+ public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
+ final String jsonData, final OffsetDateTime observedTimestamp) {
+ final Collection<DataNode> newListElements =
+ buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonData);
+ cpsDataPersistenceService.replaceListContent(dataspaceName, anchorName, parentNodeXpath, newListElements);
processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp);
}
@Override
- public void deleteListNodeData(final String dataspaceName, final String anchorName, final String listNodeXpath,
+ public void deleteListOrListElement(final String dataspaceName, final String anchorName, final String listNodeXpath,
final OffsetDateTime observedTimestamp) {
- cpsDataPersistenceService.deleteListDataNodes(dataspaceName, anchorName, listNodeXpath);
+ cpsDataPersistenceService.deleteListDataNode(dataspaceName, anchorName, listNodeXpath);
processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp);
}
-
- private DataNode buildDataNodeFromJson(final String dataspaceName, final String anchorName,
- final String parentNodeXpath, final String jsonData) {
+ private DataNode buildDataNode(final String dataspaceName, final String anchorName,
+ final String parentNodeXpath, final String jsonData) {
final var anchor = cpsAdminService.getAnchor(dataspaceName, anchorName);
final var schemaContext = getSchemaContext(dataspaceName, anchor.getSchemaSetName());
@@ -157,8 +158,10 @@ public class CpsDataServiceImpl implements CpsDataService {
.build();
}
- private Collection<DataNode> buildDataNodeCollectionFromJson(final String dataspaceName, final String anchorName,
- final String parentNodeXpath, final String jsonData) {
+ private Collection<DataNode> buildDataNodes(final String dataspaceName,
+ final String anchorName,
+ final String parentNodeXpath,
+ final String jsonData) {
final var anchor = cpsAdminService.getAnchor(dataspaceName, anchorName);
final var schemaContext = getSchemaContext(dataspaceName, anchor.getSchemaSetName());
@@ -169,7 +172,7 @@ public class CpsDataServiceImpl implements CpsDataService {
.withNormalizedNodeTree(normalizedNode)
.buildCollection();
if (dataNodes.isEmpty()) {
- throw new DataValidationException("Invalid list data.", "List node is empty.");
+ throw new DataValidationException("Invalid data.", "No data nodes provided");
}
return dataNodes;
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 bf8dd1a073..b8c472f277 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
@@ -55,16 +55,16 @@ public interface CpsDataPersistenceService {
@NonNull DataNode dataNode);
/**
- * Adds list node child elements to a Fragment.
+ * Adds list child elements to a Fragment.
*
- * @param dataspaceName dataspace name
- * @param anchorName anchor name
- * @param parentNodeXpath parent node xpath
- * @param dataNodes collection of data nodes representing list node elements
+ * @param dataspaceName dataspace name
+ * @param anchorName anchor name
+ * @param parentNodeXpath parent node xpath
+ * @param listElementsCollection collection of data nodes representing list elements
*/
- void addListDataNodes(@NonNull String dataspaceName, @NonNull String anchorName, @NonNull String parentNodeXpath,
- @NonNull Collection<DataNode> dataNodes);
+ void addListElements(@NonNull String dataspaceName, @NonNull String anchorName, @NonNull String parentNodeXpath,
+ @NonNull Collection<DataNode> listElementsCollection);
/**
* Retrieves datanode by XPath for given dataspace and anchor.
@@ -101,25 +101,36 @@ public interface CpsDataPersistenceService {
void replaceDataNodeTree(@NonNull String dataspaceName, @NonNull String anchorName, @NonNull DataNode dataNode);
/**
- * Replaces existing list data node content including descendants.
+ * Replaces list content by removing all existing elements and inserting the given new elements
+ * under given parent, anchor and dataspace.
*
* @param dataspaceName dataspace name
* @param anchorName anchor name
* @param parentNodeXpath parent node xpath
- * @param dataNodes collection of data nodes representing list node elements
+ * @param newListElements collection of data nodes representing the new list content
+ */
+ void replaceListContent(@NonNull String dataspaceName, @NonNull String anchorName,
+ @NonNull String parentNodeXpath, @NonNull Collection<DataNode> newListElements);
+
+ /**
+ * Deletes any dataNode, yang container or yang list or yang list element.
+ *
+ * @param dataspaceName dataspace name
+ * @param anchorName anchor name
+ * @param targetXpath xpath to list or list element (include [@key=value] to delete a single list element)
*/
- void replaceListDataNodes(@NonNull String dataspaceName, @NonNull String anchorName,
- @NonNull String parentNodeXpath, @NonNull Collection<DataNode> dataNodes);
+ void deleteDataNode(@NonNull String dataspaceName, @NonNull String anchorName,
+ @NonNull String targetXpath);
/**
- * Deletes existing list data node content including descendants.
+ * Deletes existing a single list element or the whole list.
*
* @param dataspaceName dataspace name
* @param anchorName anchor name
- * @param listNodeXpath list node xpath
+ * @param targetXpath xpath to list or list element (include [@key=value] to delete a single list element)
*/
- void deleteListDataNodes(@NonNull String dataspaceName, @NonNull String anchorName,
- @NonNull String listNodeXpath);
+ void deleteListDataNode(@NonNull String dataspaceName, @NonNull String anchorName,
+ @NonNull String targetXpath);
/**
* Get a datanode by cps path.
diff --git a/cps-service/src/main/java/org/onap/cps/spi/exceptions/DataNodeNotFoundException.java b/cps-service/src/main/java/org/onap/cps/spi/exceptions/DataNodeNotFoundException.java
index 848d5692c1..b717a2b183 100755
--- a/cps-service/src/main/java/org/onap/cps/spi/exceptions/DataNodeNotFoundException.java
+++ b/cps-service/src/main/java/org/onap/cps/spi/exceptions/DataNodeNotFoundException.java
@@ -31,6 +31,21 @@ public class DataNodeNotFoundException extends DataValidationException {
/**
* Constructor.
*
+ * @param dataspaceName the name of the dataspace
+ * @param anchorName the anchor name
+ * @param xpath datanode xpath
+ * @param additionalInformation additional information
+ */
+ public DataNodeNotFoundException(final String dataspaceName, final String anchorName, final String xpath,
+ final String additionalInformation) {
+ super("DataNode not found", String
+ .format("DataNode with xpath %s was not found for anchor %s and dataspace %s, %s.", xpath,
+ anchorName, dataspaceName, additionalInformation));
+ }
+
+ /**
+ * Constructor.
+ *
* @param dataspaceName the name of the dataspace
* @param anchorName the anchor name
* @param xpath datanode xpath
diff --git a/cps-service/src/main/java/org/onap/cps/utils/DataMapUtils.java b/cps-service/src/main/java/org/onap/cps/utils/DataMapUtils.java
index 3ee6afb719..71a95f1ca0 100644
--- a/cps-service/src/main/java/org/onap/cps/utils/DataMapUtils.java
+++ b/cps-service/src/main/java/org/onap/cps/utils/DataMapUtils.java
@@ -58,7 +58,7 @@ public class DataMapUtils {
return ImmutableMap.<String, Object>builder()
.putAll(
dataNodes.stream()
- .filter(dataNode -> isListNode(dataNode.getXpath()))
+ .filter(dataNode -> isListElement(dataNode.getXpath()))
.collect(groupingBy(
dataNode -> getNodeIdentifier(dataNode.getXpath()),
mapping(DataMapUtils::toDataMap, toUnmodifiableList())
@@ -86,10 +86,10 @@ public class DataMapUtils {
}
private static boolean isContainerNode(final String xpath) {
- return !isListNode(xpath);
+ return !isListElement(xpath);
}
- private static boolean isListNode(final String xpath) {
+ private static boolean isListElement(final String xpath) {
return xpath.endsWith("]");
}
}
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 6a0a4649a6..2bd44824a6 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
@@ -22,7 +22,6 @@
package org.onap.cps.api.impl
-import java.time.OffsetDateTime
import org.onap.cps.TestUtils
import org.onap.cps.api.CpsAdminService
import org.onap.cps.api.CpsModuleService
@@ -36,6 +35,8 @@ import org.onap.cps.yang.YangTextSchemaSourceSet
import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
import spock.lang.Specification
+import java.time.OffsetDateTime
+
class CpsDataServiceImplSpec extends Specification {
def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService)
def mockCpsAdminService = Mock(CpsAdminService)
@@ -84,14 +85,14 @@ class CpsDataServiceImplSpec extends Specification {
1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp)
}
- def 'Saving list-node data fragment under existing node.'() {
+ def 'Saving list element data fragment under existing node.'() {
given: 'schema set for given anchor and dataspace references test-tree model'
setupSchemaSetMocks('test-tree.yang')
- when: 'save data method is invoked with list-node json data'
+ when: 'save data method is invoked with list element json data'
def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
- objectUnderTest.saveListNodeData(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
+ objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
then: 'the persistence service method is invoked with correct parameters'
- 1 * mockCpsDataPersistenceService.addListDataNodes(dataspaceName, anchorName, '/test-tree',
+ 1 * mockCpsDataPersistenceService.addListElements(dataspaceName, anchorName, '/test-tree',
{ dataNodeCollection ->
{
assert dataNodeCollection.size() == 2
@@ -104,12 +105,12 @@ class CpsDataServiceImplSpec extends Specification {
1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp)
}
- def 'Saving empty list-node data fragment.'() {
+ def 'Saving empty list element data fragment.'() {
given: 'schema set for given anchor and dataspace references test-tree model'
setupSchemaSetMocks('test-tree.yang')
- when: 'save data method is invoked with empty list-node data fragment'
+ when: 'save data method is invoked with an empty list'
def jsonData = '{"branch": []}'
- objectUnderTest.saveListNodeData(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
+ objectUnderTest.saveListElements(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
then: 'invalid data exception is thrown'
thrown(DataValidationException)
}
@@ -185,14 +186,14 @@ 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.'() {
+ def 'Replace list content data fragment under parent 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'
+ when: 'replace list data method is invoked with list element json data'
def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
- objectUnderTest.replaceListNodeData(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
+ objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
then: 'the persistence service method is invoked with correct parameters'
- 1 * mockCpsDataPersistenceService.replaceListDataNodes(dataspaceName, anchorName, '/test-tree',
+ 1 * mockCpsDataPersistenceService.replaceListContent(dataspaceName, anchorName, '/test-tree',
{ dataNodeCollection ->
{
assert dataNodeCollection.size() == 2
@@ -205,23 +206,23 @@ class CpsDataServiceImplSpec extends Specification {
1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp)
}
- def 'Replace with empty list-node data fragment.'() {
+ def 'Replace whole list content with empty list element.'() {
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'
+ when: 'replace list data method is invoked with empty list'
def jsonData = '{"branch": []}'
- objectUnderTest.replaceListNodeData(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
+ objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
then: 'invalid data exception is thrown'
thrown(DataValidationException)
}
- def 'Delete list-node data fragment under existing node.'() {
+ def 'Delete list element 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', observedTimestamp)
+ when: 'delete list data method is invoked with list element json data'
+ objectUnderTest.deleteListOrListElement(dataspaceName, anchorName, '/test-tree/branch', observedTimestamp)
then: 'the persistence service method is invoked with correct parameters'
- 1 * mockCpsDataPersistenceService.deleteListDataNodes(dataspaceName, anchorName, '/test-tree/branch')
+ 1 * mockCpsDataPersistenceService.deleteListDataNode(dataspaceName, anchorName, '/test-tree/branch')
and: 'data updated event is sent to notification service'
1 * mockNotificationService.processDataUpdatedEvent(dataspaceName, anchorName, observedTimestamp)
}
diff --git a/cps-service/src/test/groovy/org/onap/cps/model/DataNodeBuilderSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/model/DataNodeBuilderSpec.groovy
index 2751d55075..7af1ed41c7 100644
--- a/cps-service/src/test/groovy/org/onap/cps/model/DataNodeBuilderSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/model/DataNodeBuilderSpec.groovy
@@ -140,9 +140,9 @@ class DataNodeBuilderSpec extends Specification {
given: 'a schema context for expected model'
def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('test-tree.yang')
def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext()
- and: 'parent node xpath referencing parent of list-node element'
+ and: 'parent node xpath referencing parent of list element'
def parentNodeXpath = "/test-tree"
- and: 'the json data fragment (list-node element) parsed into normalized node object'
+ and: 'the json data fragment (list element) parsed into normalized node object'
def normalizedNode = YangUtils.parseJsonData(jsonData, schemaContext, parentNodeXpath)
when: 'the normalized node is converted to a data node collection'
def result = new DataNodeBuilder().withNormalizedNodeTree(normalizedNode)
diff --git a/cps-service/src/test/groovy/org/onap/cps/spi/exceptions/CpsExceptionsSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/spi/exceptions/CpsExceptionsSpec.groovy
index 8a0e0c82e7..4243c18c24 100755
--- a/cps-service/src/test/groovy/org/onap/cps/spi/exceptions/CpsExceptionsSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/spi/exceptions/CpsExceptionsSpec.groovy
@@ -30,6 +30,7 @@ class CpsExceptionsSpec extends Specification {
def providedMessage = 'some message'
def providedDetails = 'some details'
def xpath = 'some xpath'
+ def additionalInformation = 'some information'
def 'Creating an exception that the Anchor already exist.'() {
given: 'an exception dat the Anchor already exist is created'
@@ -133,6 +134,12 @@ class CpsExceptionsSpec extends Specification {
== "DataNode not found for anchor ${anchorName} and dataspace ${dataspaceName}."
}
+ def 'Creating a exception that a datanode with a specified xpath with additional information does not exist.'() {
+ expect: 'the exception details contains the correct message with dataspace name and anchor.'
+ (new DataNodeNotFoundException(dataspaceName, anchorName, xpath, additionalInformation)).details
+ == "DataNode with xpath ${xpath} was not found for anchor ${anchorName} and dataspace ${dataspaceName}, ${additionalInformation}."
+ }
+
def 'Creating a exception that a dataspace already exists.'() {
expect: 'the exception details contains the correct message with dataspace name.'
(AlreadyDefinedException.forDataspace(dataspaceName, rootCause)).details