summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDylanB95EST <dylan.byrne@est.tech>2021-10-29 17:33:06 +0100
committerDylanB95EST <dylan.byrne@est.tech>2021-11-02 11:59:14 +0000
commita79c9f1bdf335843c29a425da53c15b5e353e5a3 (patch)
tree4299454209e6187220728440160c7934db31c825
parent717215a36dde7bedb4257e693650c8728056b9d6 (diff)
Clean Up Code around List Nodes
Make sure code refers clearly to List (whole) nodes or List elements incl. method names, parameter names, test descriptions etc. Issue-ID: CPS-756 Change-Id: Ic9dae6565c0e84c1ba4c2d6e891d3ea307f589da Signed-off-by: DylanB95EST <dylan.byrne@est.tech>
-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