From: Ruslan Kashapov Date: Mon, 19 Apr 2021 09:40:01 +0000 (+0300) Subject: Create child data node (part 1): CPS service + REST X-Git-Tag: 1.1.0~80 X-Git-Url: https://gerrit.onap.org/r/gitweb?p=cps.git;a=commitdiff_plain;h=24bf350947acb7fcb62878932d520387bc922a96 Create child data node (part 1): CPS service + REST Issue-ID: CPS-337 Change-Id: I9c5c62d144b5301ac80e2b82a5cc66a980dad011 Signed-off-by: Ruslan Kashapov --- diff --git a/cps-rest/docs/api/swagger/cpsData.yml b/cps-rest/docs/api/swagger/cpsData.yml index 24644899a..54c89661b 100755 --- a/cps-rest/docs/api/swagger/cpsData.yml +++ b/cps-rest/docs/api/swagger/cpsData.yml @@ -33,6 +33,7 @@ nodesByDataspaceAndAnchor: parameters: - $ref: 'components.yml#/components/parameters/dataspaceNameInPath' - $ref: 'components.yml#/components/parameters/anchorNameInPath' + - $ref: 'components.yml#/components/parameters/xpathInQuery' requestBody: required: true content: 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 f466ebcef..3385f35fe 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 @@ -21,8 +21,6 @@ package org.onap.cps.rest.controller; -import javax.validation.Valid; -import javax.validation.constraints.NotNull; import org.onap.cps.api.CpsDataService; import org.onap.cps.rest.api.CpsDataApi; import org.onap.cps.spi.FetchDescendantsOption; @@ -38,13 +36,19 @@ import org.springframework.web.bind.annotation.RestController; @RequestMapping("${rest.api.cps-base-path}") public class DataRestController implements CpsDataApi { + private static final String ROOT_XPATH = "/"; + @Autowired private CpsDataService cpsDataService; @Override - public ResponseEntity createNode(@Valid final String jsonData, @NotNull final String dataspaceName, - @NotNull @Valid final String anchorName) { - cpsDataService.saveData(dataspaceName, anchorName, jsonData); + public ResponseEntity createNode(final String jsonData, final String dataspaceName, final String anchorName, + final String parentNodeXpath) { + if (isRootXpath(parentNodeXpath)) { + cpsDataService.saveData(dataspaceName, anchorName, jsonData); + } else { + cpsDataService.saveData(dataspaceName, anchorName, parentNodeXpath, jsonData); + } return new ResponseEntity<>(HttpStatus.CREATED); } @@ -56,7 +60,7 @@ public class DataRestController implements CpsDataApi { @Override public ResponseEntity getNodeByDataspaceAndAnchor(final String dataspaceName, final String anchorName, final String xpath, final Boolean includeDescendants) { - if ("/".equals(xpath)) { + if (isRootXpath(xpath)) { return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } final FetchDescendantsOption fetchDescendantsOption = Boolean.TRUE.equals(includeDescendants) @@ -79,4 +83,8 @@ public class DataRestController implements CpsDataApi { cpsDataService.replaceNodeTree(dataspaceName, anchorName, parentNodeXpath, jsonData); return new ResponseEntity<>(HttpStatus.OK); } + + private static boolean isRootXpath(final String xpath) { + return ROOT_XPATH.equals(xpath); + } } 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 ef43641ea..713dda140 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 @@ -90,7 +90,8 @@ class DataRestControllerSpec extends Specification { dataNodeBaseEndpoint = "$basePath/v1/dataspaces/$dataspaceName" } - def 'Create a node.'() { + @Unroll + def 'Create a node: #scenario.'() { given: 'some json to create a data node' def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes" def json = 'some json (this is not validated)' @@ -98,12 +99,38 @@ class DataRestControllerSpec extends Specification { def response = mvc.perform( post(endpoint) - .contentType(MediaType.APPLICATION_JSON).content(json)) - .andReturn().response + .contentType(MediaType.APPLICATION_JSON) + .param('xpath', parentNodeXpath) + .content(json) + ).andReturn().response then: 'a created response is returned' response.status == HttpStatus.CREATED.value() then: 'the java API was called with the correct parameters' 1 * mockCpsDataService.saveData(dataspaceName, anchorName, json) + where: 'following xpath parameters are are used' + scenario | parentNodeXpath + 'no xpath parameter' | '' + 'xpath parameter point root' | '/' + } + + def 'Create a child node'() { + given: 'some json to create a data node' + def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes" + def json = 'some json (this is not validated)' + and: 'parent node xpath' + def parentNodeXpath = 'some xpath' + when: 'post is invoked with datanode endpoint and json' + def response = + mvc.perform( + post(endpoint) + .contentType(MediaType.APPLICATION_JSON) + .param('xpath', parentNodeXpath) + .content(json) + ).andReturn().response + then: 'a created response is returned' + response.status == HttpStatus.CREATED.value() + then: 'the java API was called with the correct parameters' + 1 * mockCpsDataService.saveData(dataspaceName, anchorName, parentNodeXpath, json) } @Unroll diff --git a/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java b/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java index 54d925891..8552c6c0d 100644 --- a/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java +++ b/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java @@ -22,6 +22,8 @@ package org.onap.cps.api; import org.checkerframework.checker.nullness.qual.NonNull; import org.onap.cps.spi.FetchDescendantsOption; +import org.onap.cps.spi.exceptions.AlreadyDefinedException; +import org.onap.cps.spi.exceptions.DataNodeNotFoundException; import org.onap.cps.spi.exceptions.DataValidationException; import org.onap.cps.spi.model.DataNode; @@ -40,6 +42,20 @@ public interface CpsDataService { */ void saveData(@NonNull String dataspaceName, @NonNull String anchorName, @NonNull String jsonData); + /** + * Persists child data fragment under existing data node for the given anchor and dataspace. + * + * @param dataspaceName dataspace name + * @param anchorName anchor name + * @param parentNodeXpath parent node xpath + * @param jsonData json data + * @throws DataValidationException when json data is invalid + * @throws DataNodeNotFoundException when parent node cannot be found by parent node xpath + * @throws AlreadyDefinedException when child data node with same xpath already exists + */ + void saveData(@NonNull String dataspaceName, @NonNull String anchorName, @NonNull String parentNodeXpath, + @NonNull String jsonData); + /** * Retrieves datanode by XPath for given dataspace and anchor. * diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java index 6f7d6439b..cc290bf29 100755 --- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java +++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java @@ -58,6 +58,13 @@ public class CpsDataServiceImpl implements CpsDataService { cpsDataPersistenceService.storeDataNode(dataspaceName, anchorName, dataNode); } + @Override + public void saveData(final String dataspaceName, final String anchorName, final String parentNodeXpath, + final String jsonData) { + final DataNode dataNode = buildDataNodeFromJson(dataspaceName, anchorName, parentNodeXpath, jsonData); + cpsDataPersistenceService.addChildDataNode(dataspaceName, anchorName, parentNodeXpath, dataNode); + } + @Override public DataNode getDataNode(final String dataspaceName, final String anchorName, final String xpath, final FetchDescendantsOption fetchDescendantsOption) { diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy index d56147527..29a2314a3 100644 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy @@ -1,6 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2021 Nordix Foundation + * Modifications Copyright (C) 2021 Pantheon.tech * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -69,6 +70,25 @@ class CpsDataServiceImplSpec extends Specification { { dataNode -> dataNode.xpath == '/test-tree' }) } + def 'Saving child data fragment under existing node.'() { + given: 'that the admin service will return an anchor' + def anchor = Anchor.builder().name(anchorName).schemaSetName(schemaSetName).build() + mockCpsAdminService.getAnchor(dataspaceName, anchorName) >> anchor + and: 'the schema source set cache returns a schema source set' + def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet) + mockYangTextSchemaSourceSetCache.get(dataspaceName, schemaSetName) >> mockYangTextSchemaSourceSet + and: 'the schema source sets returns the test-tree schema context' + def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('test-tree.yang') + def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext() + mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext + when: 'save data method is invoked with test-tree json data' + def jsonData = '{"branch": [{"name": "New"}]}' + objectUnderTest.saveData(dataspaceName, anchorName, '/test-tree',jsonData) + then: 'the persistence service method is invoked with correct parameters' + 1 * mockCpsDataPersistenceService.addChildDataNode(dataspaceName, anchorName,'/test-tree', + { dataNode -> dataNode.xpath == '/test-tree/branch[@name=\'New\']' }) + } + @Unroll def 'Get data node with option #fetchDescendantsOption.'() { def xpath = '/xpath'