XML content support on Replace list content 43/138943/24
authorRudrangi Anupriya <ra00745022@techmahindra.com>
Wed, 27 Nov 2024 18:19:42 +0000 (23:49 +0530)
committerRudrangi Anupriya <ra00745022@techmahindra.com>
Thu, 28 Nov 2024 07:00:33 +0000 (12:30 +0530)
Here to bring Support for XML Response Entity in Replace List content

- Add ContentTypeInheadr in cpsData.yml to support application/xml
- Add contentTypeInHeader parameter to accept xml  in DataRestController.java
- Modify the code return xml Data
- written testcase for above changes made

Issue-ID: CPS-2411
Change-Id: Ibb7ffb66ccdd03703266123c6d5c2eade0e7cb4a
Signed-off-by: Rudrangi Anupriya <ra00745022@techmahindra.com>
cps-rest/docs/openapi/cpsData.yml
cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java
cps-rest/src/main/java/org/onap/cps/rest/controller/QueryRestController.java
cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
cps-service/src/main/java/org/onap/cps/api/CpsDataService.java
cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
docs/api/swagger/cps/openapi.yaml
integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/DataServiceIntegrationSpec.groovy
integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/UpdatePerfTest.groovy

index daf59bb..36000fd 100644 (file)
@@ -71,15 +71,24 @@ listElementByDataspaceAndAnchor:
       - $ref: 'components.yml#/components/parameters/anchorNameInPath'
       - $ref: 'components.yml#/components/parameters/requiredXpathInQuery'
       - $ref: 'components.yml#/components/parameters/observedTimestampInQuery'
+      - $ref: 'components.yml#/components/parameters/contentTypeInHeader'
     requestBody:
       required: true
       content:
         application/json:
           schema:
-            type: object
+            type: string
           examples:
             dataSample:
               $ref: 'components.yml#/components/examples/dataSample'
+        application/xml:
+          schema:
+            type: object
+            xml:
+              name: stores
+          examples:
+            dataSample:
+              $ref: 'components.yml#/components/examples/dataSampleXml'
     responses:
       '200':
         $ref: 'components.yml#/components/responses/Ok'
index dda88e0..3efb6b4 100755 (executable)
@@ -173,9 +173,11 @@ public class DataRestController implements CpsDataApi {
     @Override
     public ResponseEntity<Object> replaceListContent(final String apiVersion, final String dataspaceName,
                                                      final String anchorName, final String parentNodeXpath,
-                                                     final Object jsonData, final String observedTimestamp) {
+                                                     final String nodeData, final String observedTimestamp,
+                                                     final String contentTypeInHeader) {
+        final ContentType contentType = ContentType.fromString(contentTypeInHeader);
         cpsDataService.replaceListContent(dataspaceName, anchorName, parentNodeXpath,
-                jsonObjectMapper.asJsonString(jsonData), toOffsetDateTime(observedTimestamp));
+                nodeData, toOffsetDateTime(observedTimestamp), contentType);
         return new ResponseEntity<>(HttpStatus.OK);
     }
 
@@ -225,10 +227,10 @@ public class DataRestController implements CpsDataApi {
         return new ResponseEntity<>(jsonObjectMapper.asJsonString(deltaBetweenAnchors), HttpStatus.OK);
     }
 
-    ResponseEntity<Object> buildResponseEntity(final List<Map<String, Object>> dataMaps,
+    private ResponseEntity<Object> buildResponseEntity(final List<Map<String, Object>> dataMaps,
                                                final ContentType contentType) {
         final String responseData;
-        if (contentType == ContentType.XML) {
+        if (ContentType.XML.equals(contentType)) {
             responseData = XmlFileUtils.convertDataMapsToXml(dataMaps);
         } else {
             responseData = jsonObjectMapper.asJsonString(dataMaps);
index b425333..c419a81 100644 (file)
@@ -160,7 +160,7 @@ public class QueryRestController implements CpsQueryApi {
     private ResponseEntity<Object> buildResponseEntity(final List<Map<String, Object>> dataNodesAsListOfMaps,
                                                final ContentType contentType) {
         final String responseData;
-        if (contentType == ContentType.XML) {
+        if (ContentType.XML.equals(contentType)) {
             responseData = XmlFileUtils.convertDataMapsToXml(dataNodesAsListOfMaps);
         } else {
             responseData = jsonObjectMapper.asJsonString(dataNodesAsListOfMaps);
index 72ae4c7..892963c 100755 (executable)
@@ -576,7 +576,28 @@ class DataRestControllerSpec extends Specification {
             response.status == expectedHttpStatus.value()
         and: 'the java API was called with the correct parameters'
             expectedApiCount * mockCpsDataService.replaceListContent(dataspaceName, anchorName, 'parent xpath', expectedJsonData,
-                { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) })
+                { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }, ContentType.JSON)
+        where:
+            scenario                          | observedTimestamp              || expectedApiCount | expectedHttpStatus
+            'with observed-timestamp'         | '2021-03-03T23:59:59.999-0400' || 1                | HttpStatus.OK
+            'without observed-timestamp'      | null                           || 1                | HttpStatus.OK
+            'with invalid observed-timestamp' | 'invalid'                      || 0                | HttpStatus.BAD_REQUEST
+    }
+
+    def 'Replace list XML content #scenario.'() {
+        when: 'list-nodes endpoint is invoked with put (update) operation'
+            def putRequestBuilder = put("$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes")
+                .contentType(MediaType.APPLICATION_XML)
+                .param('xpath', 'parent xpath')
+                .content(requestBodyXml)
+            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.replaceListContent(dataspaceName, anchorName, 'parent xpath', expectedXmlData,
+                { it == DateTimeUtility.toOffsetDateTime(observedTimestamp) }, ContentType.XML)
         where:
             scenario                          | observedTimestamp              || expectedApiCount | expectedHttpStatus
             'with observed-timestamp'         | '2021-03-03T23:59:59.999-0400' || 1                | HttpStatus.OK
index b3eff8e..29c8ad0 100644 (file)
@@ -172,11 +172,12 @@ public interface CpsDataService {
      * @param dataspaceName     dataspace name
      * @param anchorName        anchor name
      * @param parentNodeXpath   parent node xpath
-     * @param jsonData          json data representing the new list elements
+     * @param nodeData          node data representing the new list elements
      * @param observedTimestamp observedTimestamp
+     * @param contentType       JSON/XML content type
      */
-    void replaceListContent(String dataspaceName, String anchorName, String parentNodeXpath, String jsonData,
-        OffsetDateTime observedTimestamp);
+    void replaceListContent(String dataspaceName, String anchorName, String parentNodeXpath, String nodeData,
+        OffsetDateTime observedTimestamp, ContentType contentType);
 
     /**
      * Replaces list content by removing all existing elements and inserting the given new elements as data nodes
index b1b545b..a63b3e5 100644 (file)
@@ -281,11 +281,11 @@ public class CpsDataServiceImpl implements CpsDataService {
     @Timed(value = "cps.data.service.list.update",
         description = "Time taken to update a list")
     public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
-            final String jsonData, final OffsetDateTime observedTimestamp) {
+            final String nodeData, final OffsetDateTime observedTimestamp, final ContentType contentType) {
         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
         final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName);
         final Collection<DataNode> newListElements =
-            buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, jsonData, ContentType.JSON);
+            buildDataNodesWithParentNodeXpath(anchor, parentNodeXpath, nodeData, contentType);
         replaceListContent(dataspaceName, anchorName, parentNodeXpath, newListElements, observedTimestamp);
     }
 
index 8c208a1..1543fb9 100644 (file)
@@ -448,12 +448,12 @@ class CpsDataServiceImplSpec extends Specification {
             assert thrownUp == originalException
     }
 
-    def 'Replace list content data fragment under parent node.'() {
+    def 'Replace list content data fragment JSON 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 element json data'
             def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
-            objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
+            objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp, ContentType.JSON)
         then: 'the persistence service method is invoked with correct parameters'
             1 * mockCpsDataPersistenceService.replaceListContent(dataspaceName, anchorName, '/test-tree',
                 { dataNodeCollection ->
@@ -468,12 +468,42 @@ class CpsDataServiceImplSpec extends Specification {
             2 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
     }
 
+    def 'Replace list content data fragment XML 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 element xml data'
+            def nodeData = '<branch><name>A</name></branch>'
+            objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', nodeData, observedTimestamp, ContentType.XML)
+        then: 'the persistence service method is invoked with correct parameters'
+            1 * mockCpsDataPersistenceService.replaceListContent(dataspaceName, anchorName, '/test-tree',
+                { dataNodeCollection ->
+                    {
+                        assert dataNodeCollection.size() == 1
+                        assert dataNodeCollection.collect { it.getXpath() }
+                            .containsAll(['/test-tree/branch[@name=\'A\']'])
+                    }
+                }
+            )
+        and: 'the CpsValidator is called on the dataspaceName and AnchorName twice'
+            2 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
+    }
+
     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'
             def jsonData = '{"branch": []}'
-            objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp)
+            objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', jsonData, observedTimestamp, ContentType.JSON)
+        then: 'invalid data exception is thrown'
+            thrown(DataValidationException)
+    }
+
+    def 'Replace whole list content XML 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 xml empty list'
+            def nodeData = '[]'
+            objectUnderTest.replaceListContent(dataspaceName, anchorName, '/test-tree', nodeData, observedTimestamp, ContentType.XML)
         then: 'invalid data exception is thrown'
             thrown(DataValidationException)
     }
index 812a2e4..fea6848 100644 (file)
@@ -1927,6 +1927,16 @@ paths:
         schema:
           example: 2021-03-21T00:10:34.030-0100
           type: string
+      - description: Content type in header
+        in: header
+        name: Content-Type
+        required: false
+        schema:
+          default: application/json
+          enum:
+          - application/json
+          - application/xml
+          type: string
       requestBody:
         content:
           application/json:
@@ -1934,8 +1944,17 @@ paths:
               dataSample:
                 $ref: '#/components/examples/dataSample'
                 value: null
+            schema:
+              type: string
+          application/xml:
+            examples:
+              dataSample:
+                $ref: '#/components/examples/dataSampleXml'
+                value: null
             schema:
               type: object
+              xml:
+                name: stores
         required: true
       responses:
         "200":
index d49931e..6c68a37 100644 (file)
@@ -310,9 +310,9 @@ class DataServiceIntegrationSpec extends FunctionalSpecBase {
 
     def 'Attempt to add empty lists.'() {
         when: 'the batches of new list element(s) are saved'
-            objectUnderTest.replaceListContent(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', [ ], now)
-        then: 'an admin exception is thrown'
-            thrown(CpsAdminException)
+            objectUnderTest.replaceListContent(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', [ ] as String, now, ContentType.JSON)
+        then: 'an data exception is thrown'
+            thrown(DataValidationException)
     }
 
     def 'Add child error scenario: #scenario.'() {
@@ -344,7 +344,7 @@ class DataServiceIntegrationSpec extends FunctionalSpecBase {
             assert countDataNodesInTree(objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore/categories[@code="2"]', DIRECT_CHILDREN_ONLY)) > 1
         when: 'the categories list is replaced with just category "1" and without child nodes (books)'
             def json = '{"categories": [ {"code":"' +categoryCode + '"' + childJson + '} ] }'
-            objectUnderTest.replaceListContent(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore', json, now)
+            objectUnderTest.replaceListContent(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore', json, now, ContentType.JSON)
         then: 'the new replaced category can be retrieved but has no children anymore'
             assert expectedNumberOfDataNodes == countDataNodesInTree(objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore/categories[@code="' +categoryCode + '"]', DIRECT_CHILDREN_ONLY))
         when: 'attempt to retrieve a category (code) not in the new list'
index 03abdb4..d764029 100644 (file)
@@ -94,7 +94,7 @@ class UpdatePerfTest extends CpsPerfTestBase {
             def jsonListData = generateJsonForOpenRoadmDevices(startId, totalNodes, changeLeaves)
         when: 'the container node is updated'
             resourceMeter.start()
-            objectUnderTest.replaceListContent(CPS_PERFORMANCE_TEST_DATASPACE, UPDATE_TEST_ANCHOR, '/openroadm-devices', jsonListData, now)
+            objectUnderTest.replaceListContent(CPS_PERFORMANCE_TEST_DATASPACE, UPDATE_TEST_ANCHOR, '/openroadm-devices', jsonListData, now, ContentType.JSON)
             resourceMeter.stop()
         then: 'there are the expected number of total nodes'
             assert totalNodes == countDataNodes('/openroadm-devices/openroadm-device')
@@ -118,7 +118,7 @@ class UpdatePerfTest extends CpsPerfTestBase {
     def 'Update leaves for 100 data nodes.'() {
         given: 'there are 200 existing data nodes'
             def jsonListData = generateJsonForOpenRoadmDevices(1, 200, false)
-            objectUnderTest.replaceListContent(CPS_PERFORMANCE_TEST_DATASPACE, UPDATE_TEST_ANCHOR, '/openroadm-devices', jsonListData, now)
+            objectUnderTest.replaceListContent(CPS_PERFORMANCE_TEST_DATASPACE, UPDATE_TEST_ANCHOR, '/openroadm-devices', jsonListData, now, ContentType.JSON)
         and: 'JSON for updated data leaves of 100 nodes'
             def jsonDataUpdated  = "{'openroadm-device':[" + (1..100).collect {"{'device-id':'C201-7-1A-" + it + "','status':'fail','ne-state':'jeopardy'}" }.join(",") + "]}"
         when: 'update is performed for leaves'