Merge "Added depth parameter in query nodes API."
authorToine Siebelink <toine.siebelink@est.tech>
Mon, 23 Jan 2023 12:07:27 +0000 (12:07 +0000)
committerGerrit Code Review <gerrit@onap.org>
Mon, 23 Jan 2023 12:07:27 +0000 (12:07 +0000)
16 files changed:
cps-rest/docs/openapi/components.yml
cps-rest/docs/openapi/cpsData.yml
cps-rest/docs/openapi/cpsDataV1Deprecated.yml
cps-rest/docs/openapi/cpsDataV2.yml [new file with mode: 0644]
cps-rest/docs/openapi/cpsQueryV1Deprecated.yml [moved from cps-rest/docs/openapi/cpsQuery.yml with 94% similarity]
cps-rest/docs/openapi/cpsQueryV2.yml [new file with mode: 0644]
cps-rest/docs/openapi/openapi.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-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceQueryDataNodeSpec.groovy
cps-service/src/main/java/org/onap/cps/spi/FetchDescendantsOption.java
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsQueryServiceImplSpec.groovy
cps-service/src/test/groovy/org/onap/cps/spi/FetchDescendantsOptionSpec.groovy
docs/api/swagger/cps/openapi.yaml

index e700da6..60b4ca3 100644 (file)
@@ -1,7 +1,7 @@
 # ============LICENSE_START=======================================================
 # Copyright (c) 2021-2022 Bell Canada.
 # Modifications Copyright (C) 2021-2022 Nordix Foundation
-# Modifications Copyright (C) 2022 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");
@@ -240,6 +240,15 @@ components:
         type: string
         example: 'application/json'
       required: true
+    descendantsInQuery:
+      name: descendants
+      in: query
+      description: descendents to query depth of children. allowed values are none, all, any number starting from -1
+      required: false
+      schema:
+        type: string
+        default: none
+        example: 3
 
   responses:
     NotFound:
index 0dc3887..1d60e1f 100644 (file)
@@ -1,7 +1,7 @@
 # ============LICENSE_START=======================================================
 # Copyright (c) 2021-2022 Bell Canada.
 # Modifications Copyright (C) 2021-2022 Nordix Foundation
-# Modifications Copyright (C) 2022 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");
 # SPDX-License-Identifier: Apache-2.0
 # ============LICENSE_END=========================================================
 
-nodeByDataspaceAndAnchor:
-  get:
-    description: Get a node with an option to retrieve all the children for a given anchor and dataspace
-    tags:
-      - cps-data
-    summary: Get a node
-    operationId: getNodeByDataspaceAndAnchor
-    parameters:
-      - $ref: 'components.yml#/components/parameters/apiVersionInPath'
-      - $ref: 'components.yml#/components/parameters/dataspaceNameInPath'
-      - $ref: 'components.yml#/components/parameters/anchorNameInPath'
-      - $ref: 'components.yml#/components/parameters/xpathInQuery'
-      - $ref: 'components.yml#/components/parameters/includeDescendantsOptionInQuery'
-    responses:
-      '200':
-        description: OK
-        content:
-          application/json:
-            schema:
-              type: object
-            examples:
-              dataSample:
-                $ref: 'components.yml#/components/examples/dataSample'
-      '400':
-        $ref: 'components.yml#/components/responses/BadRequest'
-      '401':
-        $ref: 'components.yml#/components/responses/Unauthorized'
-      '403':
-        $ref: 'components.yml#/components/responses/Forbidden'
-      '500':
-        $ref: 'components.yml#/components/responses/InternalServerError'
-    x-codegen-request-body-name: xpath
-
 listElementByDataspaceAndAnchor:
   post:
     description: Add list element(s) to a list for a given anchor and dataspace
index 194ca3e..67ddecd 100644 (file)
@@ -1,5 +1,5 @@
 # ============LICENSE_START=======================================================
-# Copyright (C) 2022 TechMahindra Ltd.
+# Copyright (C) 2022-2023 TechMahindra Ltd.
 # ================================================================================
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # SPDX-License-Identifier: Apache-2.0
 # ============LICENSE_END=========================================================
 
+nodeByDataspaceAndAnchor:
+  get:
+    description: Get a node with an option to retrieve all the children for a given anchor and dataspace
+    deprecated: true
+    tags:
+      - cps-data
+    summary: Get a node
+    operationId: getNodeByDataspaceAndAnchor
+    parameters:
+      - $ref: 'components.yml#/components/parameters/dataspaceNameInPath'
+      - $ref: 'components.yml#/components/parameters/anchorNameInPath'
+      - $ref: 'components.yml#/components/parameters/xpathInQuery'
+      - $ref: 'components.yml#/components/parameters/includeDescendantsOptionInQuery'
+    responses:
+      '200':
+        description: OK
+        content:
+          application/json:
+            schema:
+              type: object
+            examples:
+              dataSample:
+                $ref: 'components.yml#/components/examples/dataSample'
+      '400':
+        $ref: 'components.yml#/components/responses/BadRequest'
+      '401':
+        $ref: 'components.yml#/components/responses/Unauthorized'
+      '403':
+        $ref: 'components.yml#/components/responses/Forbidden'
+      '500':
+        $ref: 'components.yml#/components/responses/InternalServerError'
+    x-codegen-request-body-name: xpath
+
 listElementByDataspaceAndAnchor:
   delete:
     description: Delete one or all list element(s) for a given anchor and dataspace
diff --git a/cps-rest/docs/openapi/cpsDataV2.yml b/cps-rest/docs/openapi/cpsDataV2.yml
new file mode 100644 (file)
index 0000000..61663ab
--- /dev/null
@@ -0,0 +1,49 @@
+# ============LICENSE_START=======================================================
+# Copyright (c) 2022-2023 TechMahindra Ltd.
+# ================================================================================
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+# ============LICENSE_END=========================================================
+
+nodeByDataspaceAndAnchor:
+  get:
+    description: Get a node with an option to retrieve all the children for a given anchor and dataspace
+    tags:
+      - cps-data
+    summary: Get a node
+    operationId: getNodeByDataspaceAndAnchorV2
+    parameters:
+      - $ref: 'components.yml#/components/parameters/dataspaceNameInPath'
+      - $ref: 'components.yml#/components/parameters/anchorNameInPath'
+      - $ref: 'components.yml#/components/parameters/xpathInQuery'
+      - $ref: 'components.yml#/components/parameters/descendantsInQuery'
+    responses:
+      '200':
+        description: OK
+        content:
+          application/json:
+            schema:
+              type: object
+            examples:
+              dataSample:
+                $ref: 'components.yml#/components/examples/dataSample'
+      '400':
+        $ref: 'components.yml#/components/responses/BadRequest'
+      '401':
+        $ref: 'components.yml#/components/responses/Unauthorized'
+      '403':
+        $ref: 'components.yml#/components/responses/Forbidden'
+      '500':
+        $ref: 'components.yml#/components/responses/InternalServerError'
+    x-codegen-request-body-name: xpath
similarity index 94%
rename from cps-rest/docs/openapi/cpsQuery.yml
rename to cps-rest/docs/openapi/cpsQueryV1Deprecated.yml
index 45fc70c..6ec117f 100644 (file)
@@ -1,7 +1,7 @@
 #  ============LICENSE_START=======================================================
 #  Copyright (C) 2021 Nordix Foundation
 #  Modifications Copyright (c) 2022 Bell Canada.
-#  Modifications Copyright (c) 2022 TechMahindra Ltd.
+#  Modifications Copyright (c) 2023 TechMahindra Ltd.
 #  ================================================================================
 #  Licensed under the Apache License, Version 2.0 (the "License");
 #  you may not use this file except in compliance with the License.
@@ -24,9 +24,9 @@ nodesByDataspaceAndAnchorAndCpsPath:
     tags:
       - cps-query
     summary: Query data nodes
+    deprecated: true
     operationId: getNodesByDataspaceAndAnchorAndCpsPath
     parameters:
-      - $ref: 'components.yml#/components/parameters/apiVersionInPath'
       - $ref: 'components.yml#/components/parameters/dataspaceNameInPath'
       - $ref: 'components.yml#/components/parameters/anchorNameInPath'
       - $ref: 'components.yml#/components/parameters/cpsPathInQuery'
diff --git a/cps-rest/docs/openapi/cpsQueryV2.yml b/cps-rest/docs/openapi/cpsQueryV2.yml
new file mode 100644 (file)
index 0000000..5bfd1bb
--- /dev/null
@@ -0,0 +1,49 @@
+#  ============LICENSE_START=======================================================
+#  Copyright (C) 2023 TechMahindra Ltd.
+#  ================================================================================
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+#  SPDX-License-Identifier: Apache-2.0
+#  ============LICENSE_END=========================================================
+
+nodesByDataspaceAndAnchorAndCpsPath:
+  get:
+    description: Query data nodes for the given dataspace and anchor using CPS path
+    tags:
+      - cps-query
+    summary: Query data nodes
+    operationId: getNodesByDataspaceAndAnchorAndCpsPathV2
+    parameters:
+      - $ref: 'components.yml#/components/parameters/dataspaceNameInPath'
+      - $ref: 'components.yml#/components/parameters/anchorNameInPath'
+      - $ref: 'components.yml#/components/parameters/cpsPathInQuery'
+      - $ref: 'components.yml#/components/parameters/descendantsInQuery'
+    responses:
+      '200':
+        description: OK
+        content:
+          application/json:
+            schema:
+              type: object
+            examples:
+              dataSample:
+                $ref: 'components.yml#/components/examples/dataSample'
+      '400':
+        $ref: 'components.yml#/components/responses/BadRequest'
+      '401':
+        $ref: 'components.yml#/components/responses/Unauthorized'
+      '403':
+        $ref: 'components.yml#/components/responses/Forbidden'
+      '500':
+        $ref: 'components.yml#/components/responses/InternalServerError'
+    x-codegen-request-body-name: xpath
index 0918b56..0ac825a 100644 (file)
@@ -2,7 +2,7 @@
 #  Copyright (C) 2021 Nordix Foundation
 #  Modifications Copyright (C) 2021 Pantheon.tech
 #  Modifications Copyright (C) 2021 Bell Canada.
-#  Modifications Copyright (C) 2022 TechMahindra Ltd.
+#  Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
 #  ================================================================================
 #  Licensed under the Apache License, Version 2.0 (the "License");
 #  you may not use this file except in compliance with the License.
@@ -89,8 +89,11 @@ paths:
   /{apiVersion}/dataspaces/{dataspace-name}/schema-sets/{schema-set-name}:
     $ref: 'cpsAdmin.yml#/schemaSetBySchemaSetName'
 
-  /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}/node:
-    $ref: 'cpsData.yml#/nodeByDataspaceAndAnchor'
+  /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/node:
+    $ref: 'cpsDataV1Deprecated.yml#/nodeByDataspaceAndAnchor'
+
+  /v2/dataspaces/{dataspace-name}/anchors/{anchor-name}/node:
+    $ref: 'cpsDataV2.yml#/nodeByDataspaceAndAnchor'
 
   /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes:
     $ref: 'cpsData.yml#/nodesByDataspaceAndAnchor'
@@ -101,8 +104,11 @@ paths:
   /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}/list-nodes:
     $ref: 'cpsData.yml#/listElementByDataspaceAndAnchor'
 
-  /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes/query:
-    $ref: 'cpsQuery.yml#/nodesByDataspaceAndAnchorAndCpsPath'
+  /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes/query:
+    $ref: 'cpsQueryV1Deprecated.yml#/nodesByDataspaceAndAnchorAndCpsPath'
+
+  /v2/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes/query:
+    $ref: 'cpsQueryV2.yml#/nodesByDataspaceAndAnchorAndCpsPath'
 
 security:
   - basicAuth: []
index 30bed12..3a9c764 100755 (executable)
@@ -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) 2022 TechMahindra Ltd.
+ *  Modifications Copyright (C) 2023 TechMahindra Ltd.
  *  Modifications Copyright (C) 2022 Deutsche Telekom AG
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -93,8 +93,8 @@ public class DataRestController implements CpsDataApi {
     }
 
     @Override
-    public ResponseEntity<Object> getNodeByDataspaceAndAnchor(final String apiVersion,
-        final String dataspaceName, final String anchorName, final String xpath, final Boolean includeDescendants) {
+    public ResponseEntity<Object> getNodeByDataspaceAndAnchor(final String dataspaceName,
+        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,
@@ -103,6 +103,17 @@ public class DataRestController implements CpsDataApi {
         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 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);
+    }
+
     @Override
     public ResponseEntity<Object> updateNodeLeaves(final String apiVersion, final String dataspaceName,
         final String anchorName, final Object jsonData, final String parentNodeXpath, final String observedTimestamp) {
@@ -151,4 +162,5 @@ public class DataRestController implements CpsDataApi {
                 String.format("observed-timestamp must be in '%s' format", ISO_TIMESTAMP_FORMAT));
         }
     }
+
 }
index 3e162ae..81938dc 100644 (file)
@@ -2,7 +2,7 @@
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2021-2022 Nordix Foundation
  *  Modifications Copyright (C) 2022 Bell Canada.
- *  Modifications Copyright (C) 2022 TechMahindra Ltd.
+ *  Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -49,22 +49,36 @@ public class QueryRestController implements CpsQueryApi {
     private final PrefixResolver prefixResolver;
 
     @Override
-    public ResponseEntity<Object> getNodesByDataspaceAndAnchorAndCpsPath(final String apiVersion,
-        final String dataspaceName, final String anchorName, final String cpsPath, final Boolean includeDescendants) {
+    public ResponseEntity<Object> getNodesByDataspaceAndAnchorAndCpsPath(final String dataspaceName,
+        final String anchorName, final String cpsPath, final Boolean includeDescendants) {
         final FetchDescendantsOption fetchDescendantsOption = Boolean.TRUE.equals(includeDescendants)
             ? FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS : FetchDescendantsOption.OMIT_DESCENDANTS;
+        return executeNodesByDataspaceQueryAndCreateResponse(dataspaceName, anchorName, cpsPath,
+                fetchDescendantsOption);
+    }
+
+    @Override
+    public ResponseEntity<Object> getNodesByDataspaceAndAnchorAndCpsPathV2(final String dataspaceName,
+        final String anchorName, final String cpsPath, final String fetchDescendantsOptionAsString) {
+        final FetchDescendantsOption fetchDescendantsOption =
+            FetchDescendantsOption.getFetchDescendantsOption(fetchDescendantsOptionAsString);
+        return executeNodesByDataspaceQueryAndCreateResponse(dataspaceName, anchorName, cpsPath,
+                fetchDescendantsOption);
+    }
+
+    private ResponseEntity<Object> executeNodesByDataspaceQueryAndCreateResponse(final String dataspaceName,
+             final String anchorName, final String cpsPath, final FetchDescendantsOption fetchDescendantsOption) {
         final Collection<DataNode> dataNodes =
             cpsQueryService.queryDataNodes(dataspaceName, anchorName, cpsPath, fetchDescendantsOption);
-        final List<Map<String, Object>> dataMaps = new ArrayList<>(dataNodes.size());
+        final List<Map<String, Object>> dataNodesAsListOfMaps = new ArrayList<>(dataNodes.size());
         String prefix = null;
         for (final DataNode dataNode : dataNodes) {
             if (prefix == null) {
                 prefix = prefixResolver.getPrefix(dataspaceName, anchorName, dataNode.getXpath());
             }
             final Map<String, Object> dataMap = DataMapUtils.toDataMapWithIdentifier(dataNode, prefix);
-            dataMaps.add(dataMap);
+            dataNodesAsListOfMaps.add(dataMap);
         }
-
-        return new ResponseEntity<>(jsonObjectMapper.asJsonString(dataMaps), HttpStatus.OK);
+        return new ResponseEntity<>(jsonObjectMapper.asJsonString(dataNodesAsListOfMaps), HttpStatus.OK);
     }
 }
index 94f62f8..16d106b 100755 (executable)
@@ -4,6 +4,7 @@
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2021-2022 Bell Canada.
  *  Modifications Copyright (C) 2022 Deutsche Telekom AG
+ *  Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -68,6 +69,7 @@ class DataRestControllerSpec extends Specification {
     def basePath
 
     def dataNodeBaseEndpoint
+    def dataNodeBaseEndpointV2
     def dataspaceName = 'my_dataspace'
     def anchorName = 'my_anchor'
     def noTimestamp = null
@@ -94,6 +96,7 @@ class DataRestControllerSpec extends Specification {
 
     def setup() {
         dataNodeBaseEndpoint = "$basePath/v1/dataspaces/$dataspaceName"
+        dataNodeBaseEndpointV2 = "$basePath/v2/dataspaces/$dataspaceName"
     }
 
     def 'Create a node: #scenario.'() {
@@ -237,6 +240,28 @@ class DataRestControllerSpec extends Specification {
             'with descendants'          | dataNodeWithChild            | 'true'                   || 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
+        when: 'get request is performed through REST API'
+            def response =
+                mvc.perform(
+                        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'
+            assert response.contentAsString.contains('parent')
+        and: 'the response contains child is true'
+            assert response.contentAsString.contains('"child"') == true
+    }
+
     def 'Update data node leaves: #scenario.'() {
         given: 'endpoint to update a node '
             def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
index 27ca0cc..b881c38 100644 (file)
@@ -3,6 +3,7 @@
  *  Copyright (C) 2021-2022 Nordix Foundation
  *  Modifications Copyright (C) 2021-2022 Bell Canada.
  *  Modifications Copyright (C) 2021 Pantheon.tech
+ *  Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -58,13 +59,19 @@ class QueryRestControllerSpec extends Specification {
     @Value('${rest.api.cps-base-path}')
     def basePath
 
+    def dataspaceName = 'my_dataspace'
+    def anchorName = 'my_anchor'
+    def cpsPath = 'some cps-path'
+    def dataNodeEndpointV2
+
+    def setup() {
+         dataNodeEndpointV2 = "$basePath/v2/dataspaces/$dataspaceName/anchors/$anchorName/nodes/query"
+    }
+
     def 'Query data node by cps path for the given dataspace and anchor with #scenario.'() {
         given: 'service method returns a list containing a data node'
              def dataNode1 = new DataNodeBuilder().withXpath('/xpath')
                     .withLeaves([leaf: 'value', leafList: ['leaveListElement1', 'leaveListElement2']]).build()
-            def dataspaceName = 'my_dataspace'
-            def anchorName = 'my_anchor'
-            def cpsPath = 'some cps-path'
             mockCpsQueryService.queryDataNodes(dataspaceName, anchorName, cpsPath, expectedCpsDataServiceOption) >> [dataNode1, dataNode1]
         and: 'the query endpoint'
             def dataNodeEndpoint = "$basePath/v1/dataspaces/$dataspaceName/anchors/$anchorName/nodes/query"
@@ -84,4 +91,23 @@ class QueryRestControllerSpec extends Specification {
             'no descendant explicitly'  | 'false'                  || OMIT_DESCENDANTS
             'descendants'               | 'true'                   || INCLUDE_ALL_DESCENDANTS
     }
+
+   def 'Query data node v2 api by cps path for the given dataspace and anchor with #scenario.'() {
+        given: 'service method returns a list containing a data node'
+            def dataNode1 = new DataNodeBuilder().withXpath('/xpath')
+                .withLeaves([leaf: 'value', leafList: ['leaveListElement1', 'leaveListElement2']]).build()
+            mockCpsQueryService.queryDataNodes(dataspaceName, anchorName, cpsPath, { descendantsOption -> {
+                    assert descendantsOption.depth == 2}}) >> [dataNode1, dataNode1]
+        when: 'query data nodes API is invoked'
+            def response =
+                mvc.perform(
+                        get(dataNodeEndpointV2)
+                                .param('cps-path', cpsPath)
+                                .param('descendants', '2'))
+                        .andReturn().response
+        then: 'the response contains the the datanode in json format'
+            assert response.status == HttpStatus.OK.value()
+            assert response.getContentAsString().contains('{"xpath":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}}')
+    }
+
 }
index b6d2c5d..ba8425f 100644 (file)
@@ -3,6 +3,7 @@
  *  Copyright (C) 2021-2022 Nordix Foundation
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2021 Bell Canada.
+ *  Modifications Copyright (C) 2023 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -22,6 +23,7 @@
 package org.onap.cps.spi.impl
 
 import org.onap.cps.spi.CpsDataPersistenceService
+import org.onap.cps.spi.FetchDescendantsOption
 import org.onap.cps.spi.exceptions.CpsPathException
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.test.context.jdbc.Sql
@@ -41,7 +43,7 @@ class CpsDataPersistenceQueryDataNodeSpec extends CpsPersistenceSpecBase {
     @Sql([CLEAR_DATA, SET_DATA])
     def 'Cps Path query for leaf value(s) with : #scenario.'() {
         when: 'a query is executed to get a data node by the given cps path'
-            def result = objectUnderTest.queryDataNodes(DATASPACE_NAME, ANCHOR_FOR_SHOP_EXAMPLE, cpsPath, includeDescendantsOption)
+            def result = objectUnderTest.queryDataNodes(DATASPACE_NAME, ANCHOR_FOR_SHOP_EXAMPLE, cpsPath, fetchDescendantsOption)
         then: 'the correct number of parent nodes are returned'
             result.size() == expectedNumberOfParentNodes
         then: 'the correct data is returned'
@@ -49,10 +51,12 @@ class CpsDataPersistenceQueryDataNodeSpec extends CpsPersistenceSpecBase {
                 assert it.getChildDataNodes().size() == expectedNumberOfChildNodes
             }
         where: 'the following data is used'
-            scenario                      | cpsPath                                                      | includeDescendantsOption || expectedNumberOfParentNodes | expectedNumberOfChildNodes
-            'String and no descendants'   | '/shops/shop[@id=1]/categories[@code=1]/book[@title="Dune"]' | OMIT_DESCENDANTS         || 1                           | 0
-            'Integer and descendants'     | '/shops/shop[@id=1]/categories[@code=1]/book[@price=5]'      | INCLUDE_ALL_DESCENDANTS  || 1                           | 1
-            'No condition no descendants' | '/shops/shop[@id=1]/categories'                              | OMIT_DESCENDANTS         || 3                           | 0
+            scenario                          | cpsPath                                                      | fetchDescendantsOption         || expectedNumberOfParentNodes | expectedNumberOfChildNodes
+            'String and no descendants'       | '/shops/shop[@id=1]/categories[@code=1]/book[@title="Dune"]' | OMIT_DESCENDANTS               || 1                           | 0
+            'Integer and descendants'         | '/shops/shop[@id=1]/categories[@code=1]/book[@price=5]'      | INCLUDE_ALL_DESCENDANTS        || 1                           | 1
+            'No condition no descendants'     | '/shops/shop[@id=1]/categories'                              | OMIT_DESCENDANTS               || 3                           | 0
+            'Integer and level 1 descendants' | '/shops'                                                     | new FetchDescendantsOption(1)  || 1                           | 5
+            'Integer and level 2 descendants' | '/shops/shop[@id=1]'                                         | new FetchDescendantsOption(2)  || 1                           | 3
     }
 
     @Sql([CLEAR_DATA, SET_DATA])
index b80054a..0c8cddc 100644 (file)
@@ -2,6 +2,7 @@
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2021 Pantheon.tech
  *  Copyright (C) 2022 Nordix Foundation
+ *  Modifications Copyright (C) 2023 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
 
 package org.onap.cps.spi;
 
+import com.google.common.base.Strings;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import lombok.RequiredArgsConstructor;
+import org.onap.cps.spi.exceptions.DataValidationException;
 
 @RequiredArgsConstructor
 public class FetchDescendantsOption {
@@ -29,6 +34,9 @@ public class FetchDescendantsOption {
     public static final FetchDescendantsOption OMIT_DESCENDANTS = new FetchDescendantsOption(0);
     public static final FetchDescendantsOption INCLUDE_ALL_DESCENDANTS = new FetchDescendantsOption(-1);
 
+    private static final Pattern FETCH_DESCENDANTS_OPTION_PATTERN =
+        Pattern.compile("^$|^all$|^none$|^[0-9]+$|^-1$");
+
     private final int depth;
 
     /**
@@ -58,6 +66,36 @@ public class FetchDescendantsOption {
         return nextDescendantsOption;
     }
 
+    /**
+     * get fetch descendants option for given descendant.
+     *
+     * @param fetchDescendantsOptionAsString fetch descendants option string
+     * @return fetch descendants option for given descendant
+     */
+    public static FetchDescendantsOption getFetchDescendantsOption(final String fetchDescendantsOptionAsString) {
+        validateFetchDescendantsOption(fetchDescendantsOptionAsString);
+        if (Strings.isNullOrEmpty(fetchDescendantsOptionAsString)
+                || "0".equals(fetchDescendantsOptionAsString) || "none".equals(fetchDescendantsOptionAsString)) {
+            return FetchDescendantsOption.OMIT_DESCENDANTS;
+        } else if ("-1".equals(fetchDescendantsOptionAsString) || "all".equals(fetchDescendantsOptionAsString)) {
+            return FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS;
+        } else {
+            final Integer depth = Integer.valueOf(fetchDescendantsOptionAsString);
+            return new FetchDescendantsOption(depth);
+        }
+    }
+
+    private static void validateFetchDescendantsOption(final String fetchDescendantsOptionAsString) {
+        if (Strings.isNullOrEmpty(fetchDescendantsOptionAsString)) {
+            return;
+        }
+        final Matcher matcher = FETCH_DESCENDANTS_OPTION_PATTERN.matcher(fetchDescendantsOptionAsString);
+        if (!matcher.matches()) {
+            throw new DataValidationException("FetchDescendantsOption validation error.",
+                    fetchDescendantsOptionAsString + " is not valid fetch descendants option");
+        }
+    }
+
     private static void validateDepth(final int depth) {
         if (depth < -1) {
             throw new IllegalArgumentException("A depth of less than minus one is not allowed");
index 8b232b4..60286b6 100644 (file)
@@ -1,6 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2021-2022 Nordix Foundation
+ *  Modifications Copyright (C) 2023 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -43,7 +44,8 @@ class CpsQueryServiceImplSpec extends Specification {
         and: 'the CpsValidator is called on the dataspaceName, schemaSetName and anchorName'
             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
         where: 'all fetch descendants options are supported'
-            fetchDescendantsOption << [FetchDescendantsOption.OMIT_DESCENDANTS, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS]
+            fetchDescendantsOption << [FetchDescendantsOption.OMIT_DESCENDANTS, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS,
+                FetchDescendantsOption.FETCH_DIRECT_CHILDREN_ONLY, new FetchDescendantsOption(10)]
     }
 
 }
index 6273835..c4d3dd8 100644 (file)
@@ -1,6 +1,7 @@
 /*
  * ============LICENSE_START=======================================================
  *  Copyright (C) 2022 Nordix Foundation
+ *  Modifications Copyright (C) 2023 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -20,7 +21,7 @@
 
 package org.onap.cps.spi
 
-
+import org.onap.cps.spi.exceptions.DataValidationException
 import spock.lang.Specification
 
 class FetchDescendantsOptionSpec extends Specification {
@@ -72,4 +73,18 @@ class FetchDescendantsOptionSpec extends Specification {
         then: 'exception thrown'
             thrown IllegalArgumentException
     }
+
+    def 'Create fetch descendant option with  descendant using #scenario'() {
+        when: 'the next level of depth is not allowed'
+           def FetchDescendantsOption fetchDescendantsOption = FetchDescendantsOption.getFetchDescendantsOption(fetchDescendantsOptionAsString)
+        then: 'fetch descendant object created'
+            assert fetchDescendantsOption.depth == expectedDepth
+        where: 'following parameters are used'
+            scenario                            | fetchDescendantsOptionAsString || expectedDepth
+            'all descendants using number'      | '-1'                           || -1
+            'all descendants using all'         | 'all'                          || -1
+            'No descendants by default'         | ''                             || 0
+            'No descendants using none'         | 'none'                         || 0
+            'til 10th descendants using number' | '10'                           || 10
+    }
 }
index ec7d295..80766cc 100644 (file)
@@ -1104,16 +1104,16 @@ paths:
                 status: 500
                 message: Internal Server Error
                 details: Internal Server Error occurred
-  /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}/node:
+  /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/node:
     get:
       tags:
       - cps-data
       summary: Get a node
       description: Get a node with an option to retrieve all the children for a given
         anchor and dataspace
+      deprecated: true
       operationId: getNodeByDataspaceAndAnchor
       parameters:
-      - $ref: '#/components/parameters/apiVersionInPath'
       - name: dataspace-name
         in: path
         description: dataspace-name
@@ -1199,6 +1199,101 @@ paths:
                 message: Internal Server Error
                 details: Internal Server Error occurred
       x-codegen-request-body-name: xpath
+  /v2/dataspaces/{dataspace-name}/anchors/{anchor-name}/node:
+    get:
+      tags:
+      - cps-data
+      summary: Get a node
+      description: Get a node with an option to retrieve all the children for a given
+        anchor and dataspace
+      operationId: getNodeByDataspaceAndAnchorV2
+      parameters:
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: anchor-name
+        in: path
+        description: anchor-name
+        required: true
+        schema:
+          type: string
+          example: my-anchor
+      - name: xpath
+        in: query
+        description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html"
+        required: false
+        schema:
+          type: string
+          default: /
+        examples:
+          container xpath:
+            value: /shops/bookstore
+          list attributes xpath:
+            value: "/shops/bookstore/categories[@code=1]"
+      - name: descendants
+        in: query
+        description: descendants
+        required: false
+        schema:
+          type: string
+          example: 3
+          default: none
+                 pattern: '^all$|^none$|^[0-9]+$|^-1$'
+      responses:
+        "200":
+          description: OK
+          content:
+            application/json:
+              schema:
+                type: object
+              examples:
+                dataSample:
+                  $ref: '#/components/examples/dataSample'
+        "400":
+          description: Bad Request
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 400
+                message: Bad Request
+                details: The provided request is not valid
+        "401":
+          description: Unauthorized
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 401
+                message: Unauthorized request
+                details: This request is unauthorized
+        "403":
+          description: Forbidden
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 403
+                message: Request Forbidden
+                details: This request is forbidden
+        "500":
+          description: Internal Server Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 500
+                message: Internal Server Error
+                details: Internal Server Error occurred
+      x-codegen-request-body-name: xpath
   /{apiVersion}/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes:
     put:
       tags:
@@ -1972,6 +2067,101 @@ paths:
                 message: Internal Server Error
                 details: Internal Server Error occurred
       x-codegen-request-body-name: xpath
+  /v2/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes/query:
+    get:
+      tags:
+      - cps-query
+      summary: Query data nodes
+      description: Query data nodes for the given dataspace and anchor using CPS path
+      operationId: getNodesByDataspaceAndAnchorAndCpsPathV2
+      parameters:
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: anchor-name
+        in: path
+        description: anchor-name
+        required: true
+        schema:
+          type: string
+          example: my-anchor
+      - name: cps-path
+        in: query
+        description: "For more details on cps path, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html"
+        required: false
+        schema:
+          type: string
+          default: /
+        examples:
+          container cps path:
+            value: //bookstore
+          list attributes cps path:
+            value: "//categories[@code=1]"
+      - name: descendants
+        in: query
+        description: descendants
+        required: false
+        schema:
+          type: string
+                 pattern: '^all$|^none$|^[0-9]+$|^-1$'
+          example: false
+          default: none
+                 example: 3
+      responses:
+        "200":
+          description: OK
+          content:
+            application/json:
+              schema:
+                type: object
+              examples:
+                dataSample:
+                  $ref: '#/components/examples/dataSample'
+        "400":
+          description: Bad Request
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 400
+                message: Bad Request
+                details: The provided request is not valid
+        "401":
+          description: Unauthorized
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 401
+                message: Unauthorized request
+                details: This request is unauthorized
+        "403":
+          description: Forbidden
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 403
+                message: Request Forbidden
+                details: This request is forbidden
+        "500":
+          description: Internal Server Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 500
+                message: Internal Server Error
+                details: Internal Server Error occurred
+      x-codegen-request-body-name: xpath
 components:
   parameters:
     apiVersionInPath: