Rest & Java API layer - Query Datanodes using cpsPath that contains contains a leaf name and a leaf value

Issue-ID: CPS-231

Signed-off-by: niamhcore <niamh.core@est.tech>
Change-Id: I3bf2a9946746ad06ddb4bc832206a36b78ff2175
diff --git a/cps-rest/docs/api/swagger/components.yml b/cps-rest/docs/api/swagger/components.yml
index 3694f36..bb1f120 100755
--- a/cps-rest/docs/api/swagger/components.yml
+++ b/cps-rest/docs/api/swagger/components.yml
@@ -75,6 +75,14 @@
       schema:
         type: string
         default: /
+    cpsPathInQuery:
+      name: cps-path
+      in: query
+      description: cps-path
+      required: false
+      schema:
+        type: string
+        default: /
     includeDescendantsOptionInQuery:
       name: include-descendants
       in: query
diff --git a/cps-rest/docs/api/swagger/cpsQuery.yml b/cps-rest/docs/api/swagger/cpsQuery.yml
new file mode 100644
index 0000000..91a4bdb
--- /dev/null
+++ b/cps-rest/docs/api/swagger/cpsQuery.yml
@@ -0,0 +1,23 @@
+nodesByDataspaceAndAnchorAndCpsPath:
+  get:
+    description: Query data nodes for the given dataspace and anchor using CPS path
+    tags:
+      - cps-query
+    summary: Query data nodes
+    operationId: getNodesByDataspaceAndAnchorAndCpsPath
+    parameters:
+      - $ref: 'components.yml#/components/parameters/dataspaceNameInPath'
+      - $ref: 'components.yml#/components/parameters/anchorNameInPath'
+      - $ref: 'components.yml#/components/parameters/cpsPathInQuery'
+    responses:
+      '200':
+        $ref: 'components.yml#/components/responses/Ok'
+      '400':
+        $ref: 'components.yml#/components/responses/BadRequest'
+      '401':
+        $ref: 'components.yml#/components/responses/Unauthorized'
+      '403':
+        $ref: 'components.yml#/components/responses/Forbidden'
+      '404':
+        $ref: 'components.yml#/components/responses/NotFound'
+    x-codegen-request-body-name: xpath
\ No newline at end of file
diff --git a/cps-rest/docs/api/swagger/openapi.yml b/cps-rest/docs/api/swagger/openapi.yml
index 2ead202..38fbebe 100755
--- a/cps-rest/docs/api/swagger/openapi.yml
+++ b/cps-rest/docs/api/swagger/openapi.yml
@@ -50,3 +50,6 @@
 
   /v1/dataspaces/{dataspace-name}/nodes:
     $ref: 'cpsData.yml#/nodesByDataspace'
+
+  /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes/query:
+    $ref: 'cpsQuery.yml#/nodesByDataspaceAndAnchorAndCpsPath'
\ No newline at end of file
diff --git a/cps-rest/src/main/java/org/onap/cps/rest/controller/QueryRestController.java b/cps-rest/src/main/java/org/onap/cps/rest/controller/QueryRestController.java
new file mode 100644
index 0000000..a8816f0
--- /dev/null
+++ b/cps-rest/src/main/java/org/onap/cps/rest/controller/QueryRestController.java
@@ -0,0 +1,48 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  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=========================================================
+ */
+
+package org.onap.cps.rest.controller;
+
+import com.google.gson.Gson;
+import java.util.Collection;
+import javax.validation.Valid;
+import org.onap.cps.api.CpsQueryService;
+import org.onap.cps.rest.api.CpsQueryApi;
+import org.onap.cps.spi.model.DataNode;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("${rest.api.cps-base-path}")
+public class QueryRestController implements CpsQueryApi {
+
+    @Autowired
+    private CpsQueryService cpsQueryService;
+
+    @Override
+    public ResponseEntity<Object> getNodesByDataspaceAndAnchorAndCpsPath(final String dataspaceName,
+        final String anchorName, @Valid final String cpsPath) {
+        final Collection<DataNode> dataNodes =
+            cpsQueryService.queryDataNodes(dataspaceName, anchorName, cpsPath);
+        return new ResponseEntity<>(new Gson().toJson(dataNodes), HttpStatus.OK);
+    }
+}
diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/AdminRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/AdminRestControllerSpec.groovy
index 926021e..ca99743 100755
--- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/AdminRestControllerSpec.groovy
+++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/AdminRestControllerSpec.groovy
@@ -2,6 +2,7 @@
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2020 Pantheon.tech
  *  Modifications Copyright (C) 2020 Bell Canada. All rights reserved.
+ *  Copyright (C) 2021 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -27,6 +28,7 @@
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
 
 import org.modelmapper.ModelMapper
+import org.onap.cps.api.CpsQueryService
 import org.onap.cps.api.CpsAdminService
 import org.onap.cps.api.CpsDataService
 import org.onap.cps.api.CpsModuleService
@@ -60,6 +62,9 @@
     CpsDataService mockCpsDataService = Mock()
 
     @SpringBean
+    CpsQueryService mockCpsQueryService = Mock()
+
+    @SpringBean
     ModelMapper modelMapper = Mock()
 
     @Autowired
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 cca94d1..b9b680d 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
@@ -28,6 +28,7 @@
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
 
 import org.modelmapper.ModelMapper
+import org.onap.cps.api.CpsQueryService
 import org.onap.cps.api.CpsAdminService
 import org.onap.cps.api.CpsDataService
 import org.onap.cps.api.CpsModuleService
@@ -60,6 +61,9 @@
     CpsAdminService mockCpsAdminService = Mock()
 
     @SpringBean
+    CpsQueryService mockCpsQueryService = Mock()
+
+    @SpringBean
     ModelMapper modelMapper = Mock()
 
     @Autowired
diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy
new file mode 100644
index 0000000..4d9a558
--- /dev/null
+++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/QueryRestControllerSpec.groovy
@@ -0,0 +1,84 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  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=========================================================
+ */
+
+package org.onap.cps.rest.controller
+
+import com.google.common.collect.ImmutableMap
+import com.google.gson.Gson
+import org.modelmapper.ModelMapper
+import org.onap.cps.api.CpsAdminService
+import org.onap.cps.api.CpsDataService
+import org.onap.cps.api.CpsModuleService
+import org.onap.cps.api.CpsQueryService
+import org.onap.cps.spi.model.DataNode
+import org.onap.cps.spi.model.DataNodeBuilder
+import org.spockframework.spring.SpringBean
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.http.HttpStatus
+import org.springframework.test.web.servlet.MockMvc
+import spock.lang.Shared
+import spock.lang.Specification
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+
+@WebMvcTest
+class QueryRestControllerSpec extends Specification {
+
+    @SpringBean
+    CpsDataService mockCpsDataService = Mock()
+
+    @SpringBean
+    CpsModuleService mockCpsModuleService = Mock()
+
+    @SpringBean
+    CpsAdminService mockCpsAdminService = Mock()
+
+    @SpringBean
+    CpsQueryService mockCpsQueryService = Mock()
+
+    @SpringBean
+    ModelMapper modelMapper = Mock()
+
+    @Autowired
+    MockMvc mvc
+
+    @Value('${rest.api.cps-base-path}')
+    def basePath
+
+    def 'Query data node by cps path for the given dataspace and anchor.'() {
+        given: 'service method returns a list containing a data node'
+            def dataspaceName = 'my_dataspace'
+            def anchorName = 'my_anchor'
+            def cpsPath = '/xpath/leaves[@leaf=\'value\']'
+            def dataNode = new DataNodeBuilder().withXpath("/xpath")
+                    .withLeaves(ImmutableMap.of("leaf", "value")).build()
+            ArrayList<DataNode> dataNodeList = new ArrayList();
+            dataNodeList.add(dataNode)
+            mockCpsQueryService.queryDataNodes(dataspaceName, anchorName, cpsPath) >> dataNodeList
+        and: 'the query endpoint'
+            def dataNodeEndpoint = "$basePath/v1/dataspaces/$dataspaceName/anchors/$anchorName/nodes/query"
+        when: 'query data nodes API is invoked'
+            def response = mvc.perform(get(dataNodeEndpoint).param('cps-path', cpsPath)).andReturn().response
+        then: 'the response contains the the datanode in json format'
+            response.status == HttpStatus.OK.value()
+            response.getContentAsString().contains(new Gson().toJson(dataNode))
+    }
+}
\ No newline at end of file
diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy
index 8b02d73..4e10e2c 100644
--- a/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy
+++ b/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy
@@ -1,6 +1,7 @@
 /*
  * ============LICENSE_START=======================================================
  *  Copyright (C) 2020 Pantheon.tech
+ *  Copyright (C) 2021 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -24,6 +25,7 @@
 import org.onap.cps.api.CpsAdminService
 import org.onap.cps.api.CpsDataService
 import org.onap.cps.api.CpsModuleService
+import org.onap.cps.api.CpsQueryService
 import org.onap.cps.spi.exceptions.AnchorAlreadyDefinedException
 import org.onap.cps.spi.exceptions.CpsException
 import org.onap.cps.spi.exceptions.DataInUseException
@@ -60,6 +62,9 @@
     CpsDataService mockCpsDataService = Mock()
 
     @SpringBean
+    CpsQueryService mockCpsQueryService = Mock()
+
+    @SpringBean
     ModelMapper modelMapper = Mock()
 
     @Autowired
diff --git a/cps-service/src/main/java/org/onap/cps/api/CpsQueryService.java b/cps-service/src/main/java/org/onap/cps/api/CpsQueryService.java
index a66e084..b432af8 100644
--- a/cps-service/src/main/java/org/onap/cps/api/CpsQueryService.java
+++ b/cps-service/src/main/java/org/onap/cps/api/CpsQueryService.java
@@ -19,9 +19,24 @@
 
 package org.onap.cps.api;
 
+import java.util.Collection;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.onap.cps.spi.model.DataNode;
+
 /*
  * Query interface for handling cps queries.
  */
 public interface CpsQueryService {
 
+    /**
+     * Get data nodes for the given dataspace and anchor by cps path.
+     *
+     * @param dataspaceName          dataspace name
+     * @param anchorName             anchor name
+     * @param cpsPath                cps path
+     * @return a collection of data nodes
+     */
+    Collection<DataNode> queryDataNodes(@NonNull String dataspaceName, @NonNull String anchorName,
+        @NonNull String cpsPath);
+
 }
diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsQueryServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsQueryServiceImpl.java
new file mode 100644
index 0000000..63d0a0f
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsQueryServiceImpl.java
@@ -0,0 +1,40 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  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=========================================================
+ */
+
+package org.onap.cps.api.impl;
+
+import java.util.Collection;
+import org.onap.cps.api.CpsQueryService;
+import org.onap.cps.spi.CpsDataPersistenceService;
+import org.onap.cps.spi.model.DataNode;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class CpsQueryServiceImpl implements CpsQueryService {
+
+    @Autowired
+    private CpsDataPersistenceService cpsDataPersistenceService;
+
+    @Override
+    public Collection<DataNode> queryDataNodes(final String dataspaceName, final String anchorName,
+        final String cpsPath) {
+        return cpsDataPersistenceService.queryDataNodes(dataspaceName, anchorName, cpsPath);
+    }
+}
\ No newline at end of file
diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsQueryServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsQueryServiceImplSpec.groovy
new file mode 100644
index 0000000..6e044b0
--- /dev/null
+++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsQueryServiceImplSpec.groovy
@@ -0,0 +1,44 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2021 Nordix Foundation
+ *  ================================================================================
+ *  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=========================================================
+ */
+
+package org.onap.cps.api.impl
+
+import org.onap.cps.spi.CpsDataPersistenceService
+import spock.lang.Specification
+
+class CpsQueryServiceImplSpec extends Specification {
+    def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService)
+
+    def objectUnderTest = new CpsQueryServiceImpl()
+
+    def setup() {
+        objectUnderTest.cpsDataPersistenceService = mockCpsDataPersistenceService
+    }
+
+    def 'Query data nodes by cps path.'() {
+        given: 'a dataspace name, an anchor name and a cps path'
+            def dataspaceName = 'some dataspace'
+            def anchorName = 'some anchor'
+            def cpsPath = '/cps-path'
+        when: 'queryDataNodes is invoked'
+            objectUnderTest.queryDataNodes(dataspaceName, anchorName, cpsPath)
+        then: 'the persistence service is called once with the correct parameters'
+            1 * mockCpsDataPersistenceService.queryDataNodes(dataspaceName, anchorName, cpsPath)
+    }
+}
\ No newline at end of file