diff options
author | arpitsingh <as00745003@techmahindra.com> | 2023-01-09 19:53:10 +0530 |
---|---|---|
committer | Arpit Singh <as00745003@techmahindra.com> | 2023-02-17 11:20:35 +0000 |
commit | 0bd192ca12ac2f768e44d0d3482785c79a881904 (patch) | |
tree | a91a96325ecaa38adaa4b9420e4655b6cbea85a3 /cps-rest | |
parent | 9575b84ab4e2db885d8761a98eaae9ff3a06aa81 (diff) |
CPS-1401 Implement V2 of GET Data Node API
- Modified the GET Data Node API so it returns all the data nodes when
xpath set to root "/"
- Fragment Repository now returns a collection of Fragment Entities
- Instead of returning only the first Fragment Entity now all fragment
entities are returned when xpath is set to root
- The Fragemnt Entities are further processed to a Collection of Data
Nodes. As opposed to singular Data Node in current implementation.
- Finally the DataRestController also returns a Collection of Data
Nodes when xpath is set to root and valid data is present
- Response body changed from JSON object to JSON Array.
- Exception handling for invalid xpath and non-existing xpath is now
done separately at persistence layer.
- Refactored code against CPS-1422
- Deprecated getDataNode method from Service and Persistence layer
- Modified V1 of Get Data Node API to use the getDataNodes method and
get the first data node from the collection returned.
- Modified NCMP to use getDataNodes method
- NCMP still does not support multiple data nodes. It retrieves the
first data node from the collection returned by getDataNodes
Signed-off-by: arpitsingh <as00745003@techmahindra.com>
Change-Id: I494a5740a53f65376d135fcb9f1e2e8900a2803e
Diffstat (limited to 'cps-rest')
3 files changed, 98 insertions, 40 deletions
diff --git a/cps-rest/docs/openapi/cpsDataV2.yml b/cps-rest/docs/openapi/cpsDataV2.yml index 61663ab3a8..ad0c299d70 100644 --- a/cps-rest/docs/openapi/cpsDataV2.yml +++ b/cps-rest/docs/openapi/cpsDataV2.yml @@ -46,4 +46,4 @@ nodeByDataspaceAndAnchor: $ref: 'components.yml#/components/responses/Forbidden' '500': $ref: 'components.yml#/components/responses/InternalServerError' - x-codegen-request-body-name: xpath + x-codegen-request-body-name: xpath
\ No newline at end of file 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 3a9c764bc6..80cfb8ce0b 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 @@ -3,7 +3,7 @@ * Copyright (C) 2020-2022 Bell Canada. * Modifications Copyright (C) 2021 Pantheon.tech * Modifications Copyright (C) 2021-2022 Nordix Foundation - * Modifications Copyright (C) 2023 TechMahindra Ltd. + * Modifications Copyright (C) 2022-2023 TechMahindra Ltd. * Modifications Copyright (C) 2022 Deutsche Telekom AG * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,6 +26,10 @@ package org.onap.cps.rest.controller; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; import javax.validation.ValidationException; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; @@ -97,21 +101,27 @@ public class DataRestController implements CpsDataApi { final String anchorName, final String xpath, final Boolean includeDescendants) { final FetchDescendantsOption fetchDescendantsOption = Boolean.TRUE.equals(includeDescendants) ? FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS : FetchDescendantsOption.OMIT_DESCENDANTS; - final DataNode dataNode = cpsDataService.getDataNode(dataspaceName, anchorName, xpath, - fetchDescendantsOption); - final String prefix = prefixResolver.getPrefix(dataspaceName, anchorName, xpath); + final DataNode dataNode = cpsDataService.getDataNodes(dataspaceName, anchorName, xpath, + fetchDescendantsOption).iterator().next(); + final String prefix = prefixResolver.getPrefix(dataspaceName, anchorName, dataNode.getXpath()); return new ResponseEntity<>(DataMapUtils.toDataMapWithIdentifier(dataNode, prefix), HttpStatus.OK); } @Override public ResponseEntity<Object> getNodeByDataspaceAndAnchorV2(final String dataspaceName, final String anchorName, - final String xpath, final String fetchDescendantsOptionAsString) { + final String xpath, + final String fetchDescendantsOptionAsString) { final FetchDescendantsOption fetchDescendantsOption = - FetchDescendantsOption.getFetchDescendantsOption(fetchDescendantsOptionAsString); - final DataNode dataNode = cpsDataService.getDataNode(dataspaceName, anchorName, xpath, - fetchDescendantsOption); - final String prefix = prefixResolver.getPrefix(dataspaceName, anchorName, xpath); - return new ResponseEntity<>(DataMapUtils.toDataMapWithIdentifier(dataNode, prefix), HttpStatus.OK); + FetchDescendantsOption.getFetchDescendantsOption(fetchDescendantsOptionAsString); + final Collection<DataNode> dataNodes = cpsDataService.getDataNodes(dataspaceName, anchorName, xpath, + fetchDescendantsOption); + final List<Map<String, Object>> dataMaps = new ArrayList<>(dataNodes.size()); + for (final DataNode dataNode: dataNodes) { + final String prefix = prefixResolver.getPrefix(dataspaceName, anchorName, dataNode.getXpath()); + final Map<String, Object> dataMap = DataMapUtils.toDataMapWithIdentifier(dataNode, prefix); + dataMaps.add(dataMap); + } + return new ResponseEntity<>(jsonObjectMapper.asJsonString(dataMaps), HttpStatus.OK); } @Override @@ -162,5 +172,4 @@ public class DataRestController implements CpsDataApi { String.format("observed-timestamp must be in '%s' format", ISO_TIMESTAMP_FORMAT)); } } - } 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 16d106ba60..d88a9cdf0b 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 @@ -25,7 +25,9 @@ package org.onap.cps.rest.controller import com.fasterxml.jackson.databind.ObjectMapper +import groovy.json.JsonSlurper import org.onap.cps.api.CpsDataService +import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.model.DataNode import org.onap.cps.spi.model.DataNodeBuilder import org.onap.cps.utils.ContentType @@ -68,7 +70,7 @@ class DataRestControllerSpec extends Specification { @Value('${rest.api.cps-base-path}') def basePath - def dataNodeBaseEndpoint + def dataNodeBaseEndpointV1 def dataNodeBaseEndpointV2 def dataspaceName = 'my_dataspace' def anchorName = 'my_anchor' @@ -87,21 +89,25 @@ class DataRestControllerSpec extends Specification { def expectedXmlData = '<?xml version=\'1.0\' encoding=\'UTF-8\'?>\n<bookstore xmlns="org:onap:ccsdk:sample">\n</bookstore>' @Shared - static DataNode dataNodeWithLeavesNoChildren = new DataNodeBuilder().withXpath('/xpath') + static DataNode dataNodeWithLeavesNoChildren = new DataNodeBuilder().withXpath('/parent-1') .withLeaves([leaf: 'value', leafList: ['leaveListElement1', 'leaveListElement2']]).build() @Shared + static DataNode dataNodeWithLeavesNoChildren2 = new DataNodeBuilder().withXpath('/parent-2') + .withLeaves([leaf: 'value']).build() + + @Shared static DataNode dataNodeWithChild = new DataNodeBuilder().withXpath('/parent') .withChildDataNodes([new DataNodeBuilder().withXpath("/parent/child").build()]).build() def setup() { - dataNodeBaseEndpoint = "$basePath/v1/dataspaces/$dataspaceName" + dataNodeBaseEndpointV1 = "$basePath/v1/dataspaces/$dataspaceName" dataNodeBaseEndpointV2 = "$basePath/v2/dataspaces/$dataspaceName" } def 'Create a node: #scenario.'() { given: 'endpoint to create a node' - def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes" + def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes" when: 'post is invoked with datanode endpoint and json' def response = mvc.perform( @@ -124,7 +130,7 @@ class DataRestControllerSpec extends Specification { def 'Create a node with observed-timestamp'() { given: 'endpoint to create a node' - def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes" + def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes" when: 'post is invoked with datanode endpoint and json' def response = mvc.perform( @@ -148,7 +154,7 @@ class DataRestControllerSpec extends Specification { def 'Create a child node #scenario'() { given: 'endpoint to create a node' - def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes" + def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes" and: 'parent node xpath' def parentNodeXpath = 'some xpath' when: 'post is invoked with datanode endpoint and json' @@ -177,7 +183,7 @@ class DataRestControllerSpec extends Specification { given: 'parent node xpath ' def parentNodeXpath = 'parent node xpath' when: 'list-node endpoint is invoked with post (create) operation' - def postRequestBuilder = post("$dataNodeBaseEndpoint/anchors/$anchorName/list-nodes") + def postRequestBuilder = post("$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes") .contentType(MediaType.APPLICATION_JSON) .param('xpath', parentNodeXpath) .content(requestBodyJson) @@ -198,9 +204,9 @@ class DataRestControllerSpec extends Specification { def 'Get data node with leaves'() { given: 'the service returns data node leaves' - def xpath = 'xpath' - def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/node" - mockCpsDataService.getDataNode(dataspaceName, anchorName, xpath, OMIT_DESCENDANTS) >> dataNodeWithLeavesNoChildren + def xpath = 'parent-1' + def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/node" + mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, OMIT_DESCENDANTS) >> [dataNodeWithLeavesNoChildren] when: 'get request is performed through REST API' def response = mvc.perform(get(endpoint).param('xpath', xpath)) @@ -208,7 +214,7 @@ class DataRestControllerSpec extends Specification { then: 'a success response is returned' response.status == HttpStatus.OK.value() then: 'the response contains the the datanode in json format' - response.getContentAsString() == '{"xpath":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}}' + response.getContentAsString() == '{"parent-1":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}}' and: 'response contains expected leaf and value' response.contentAsString.contains('"leaf":"value"') and: 'response contains expected leaf-list and values' @@ -218,8 +224,8 @@ class DataRestControllerSpec extends Specification { def 'Get data node with #scenario.'() { given: 'the service returns data node with #scenario' def xpath = 'some xPath' - def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/node" - mockCpsDataService.getDataNode(dataspaceName, anchorName, xpath, expectedCpsDataServiceOption) >> dataNode + def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/node" + mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, expectedCpsDataServiceOption) >> [dataNode] when: 'get request is performed through REST API' def response = mvc.perform( @@ -235,25 +241,68 @@ class DataRestControllerSpec extends Specification { response.contentAsString.contains('"child"') == expectChildInResponse where: scenario | dataNode | includeDescendantsOption || expectedCpsDataServiceOption | expectChildInResponse | expectedRootidentifier - 'no descendants by default' | dataNodeWithLeavesNoChildren | '' || OMIT_DESCENDANTS | false | 'xpath' - 'no descendant explicitly' | dataNodeWithLeavesNoChildren | 'false' || OMIT_DESCENDANTS | false | 'xpath' + 'no descendants by default' | dataNodeWithLeavesNoChildren | '' || OMIT_DESCENDANTS | false | 'parent-1' + 'no descendant explicitly' | dataNodeWithLeavesNoChildren | 'false' || OMIT_DESCENDANTS | false | 'parent-1' 'with descendants' | dataNodeWithChild | 'true' || INCLUDE_ALL_DESCENDANTS | true | 'parent' } + def 'Get all the data trees as json array with root node xPath using V2'() { + given: 'the service returns all data node leaves' + def xpath = '/' + def endpoint = "$dataNodeBaseEndpointV2/anchors/$anchorName/node" + mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, OMIT_DESCENDANTS) >> [dataNodeWithLeavesNoChildren, dataNodeWithLeavesNoChildren2] + when: 'V2 of get request is performed through REST API' + def response = + mvc.perform(get(endpoint).param('xpath', xpath)) + .andReturn().response + then: 'a success response is returned' + response.status == HttpStatus.OK.value() + and: 'the response contains the datanode in json array format' + response.getContentAsString() == '[{"parent-1":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}},' + + '{"parent-2":{"leaf":"value"}}]' + and: 'the json array contains expected number of data trees' + def numberOfDataTrees = new JsonSlurper().parseText(response.getContentAsString()).iterator().size() + assert numberOfDataTrees == 2 + } + + def 'Get data node with #scenario using V2.'() { + given: 'the service returns data nodes with #scenario' + def xpath = 'some xPath' + def endpoint = "$dataNodeBaseEndpointV2/anchors/$anchorName/node" + mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, expectedCpsDataServiceOption) >> [dataNode] + when: 'V2 of get request is performed through REST API' + def response = + mvc.perform( + get(endpoint) + .param('xpath', xpath) + .param('descendants', includeDescendantsOption)) + .andReturn().response + then: 'a success response is returned' + response.status == HttpStatus.OK.value() + and: 'the response contains the root node identifier: #expectedRootidentifier' + response.contentAsString.contains(expectedRootidentifier) + and: 'the response contains child is #expectChildInResponse' + response.contentAsString.contains('"child"') == expectChildInResponse + where: + scenario | dataNode | includeDescendantsOption || expectedCpsDataServiceOption | expectChildInResponse | expectedRootidentifier + 'no descendants by default' | dataNodeWithLeavesNoChildren | '' || OMIT_DESCENDANTS | false | 'parent-1' + 'no descendant explicitly' | dataNodeWithLeavesNoChildren | '0' || OMIT_DESCENDANTS | false | 'parent-1' + 'with descendants' | dataNodeWithChild | '-1' || INCLUDE_ALL_DESCENDANTS | true | 'parent' + } def 'Get data node using v2 api'() { given: 'the service returns data node' def xpath = 'some xPath' def endpoint = "$dataNodeBaseEndpointV2/anchors/$anchorName/node" - mockCpsDataService.getDataNode(dataspaceName, anchorName, xpath, {descendantsOption -> { - assert descendantsOption.depth == 2}}) >> dataNodeWithChild + mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, { descendantsOption -> { + assert descendantsOption.depth == 2}} as FetchDescendantsOption) >> [dataNodeWithChild] when: 'get request is performed through REST API' def response = mvc.perform( - get(endpoint) - .param('xpath', xpath) - .param('descendants', '2')) - .andReturn().response + get(endpoint) + .param('xpath', xpath) + .param('descendants', '2')) + .andReturn().response then: 'a success response is returned' assert response.status == HttpStatus.OK.value() and: 'the response contains the root node identifier' @@ -264,7 +313,7 @@ class DataRestControllerSpec extends Specification { def 'Update data node leaves: #scenario.'() { given: 'endpoint to update a node ' - def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes" + def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes" when: 'patch request is performed' def response = mvc.perform( @@ -286,7 +335,7 @@ class DataRestControllerSpec extends Specification { def 'Update data node leaves with observedTimestamp'() { given: 'endpoint to update a node leaves ' - def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes" + def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes" when: 'patch request is performed' def response = mvc.perform( @@ -309,7 +358,7 @@ class DataRestControllerSpec extends Specification { def 'Replace data node tree: #scenario.'() { given: 'endpoint to replace node' - def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes" + def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes" when: 'put request is performed' def response = mvc.perform( @@ -331,7 +380,7 @@ class DataRestControllerSpec extends Specification { def 'Update data node and descendants with observedTimestamp.'() { given: 'endpoint to replace node' - def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes" + def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes" when: 'put request is performed' def response = mvc.perform( @@ -354,7 +403,7 @@ class DataRestControllerSpec extends Specification { def 'Replace list content #scenario.'() { when: 'list-nodes endpoint is invoked with put (update) operation' - def putRequestBuilder = put("$dataNodeBaseEndpoint/anchors/$anchorName/list-nodes") + def putRequestBuilder = put("$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes") .contentType(MediaType.APPLICATION_JSON) .param('xpath', 'parent xpath') .content(requestBodyJson) @@ -375,7 +424,7 @@ class DataRestControllerSpec extends Specification { def 'Delete list element #scenario.'() { when: 'list-nodes endpoint is invoked with delete operation' - def deleteRequestBuilder = delete("$dataNodeBaseEndpoint/anchors/$anchorName/list-nodes") + def deleteRequestBuilder = delete("$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes") .param('xpath', 'list element xpath') if (observedTimestamp != null) deleteRequestBuilder.param('observed-timestamp', observedTimestamp) @@ -396,7 +445,7 @@ class DataRestControllerSpec extends Specification { given: 'data node xpath' def dataNodeXpath = '/dataNodeXpath' when: 'delete data node endpoint is invoked' - def deleteDataNodeRequest = delete( "$dataNodeBaseEndpoint/anchors/$anchorName/nodes") + def deleteDataNodeRequest = delete( "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes") .param('xpath', dataNodeXpath) and: 'observed timestamp is added to the parameters' if (observedTimestamp != null) |