Get Data under anchor using single root 82/120782/5
authorRishi.Chail <rishi.chail@est.tech>
Tue, 13 Apr 2021 23:51:44 +0000 (00:51 +0100)
committerRishi.Chail <rishi.chail@est.tech>
Mon, 26 Apr 2021 11:58:17 +0000 (12:58 +0100)
Issue-ID: CPS-325

Signed-off-by: Rishi.Chail <rishi.chail@est.tech>
Change-Id: Id8da3d767199c5767c625b55d175ac6791dcca48

cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java
cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java [changed mode: 0644->0755]
cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy [changed mode: 0644->0755]
cps-ri/src/test/resources/data/fragment.sql [changed mode: 0644->0755]
cps-service/src/main/java/org/onap/cps/spi/exceptions/DataNodeNotFoundException.java [changed mode: 0644->0755]
cps-service/src/test/groovy/org/onap/cps/spi/exceptions/CpsExceptionsSpec.groovy

index 3385f35..ddd3ac4 100755 (executable)
@@ -60,13 +60,10 @@ public class DataRestController implements CpsDataApi {
     @Override
     public ResponseEntity<Object> getNodeByDataspaceAndAnchor(final String dataspaceName, final String anchorName,
         final String xpath, final Boolean includeDescendants) {
-        if (isRootXpath(xpath)) {
-            return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
-        }
         final FetchDescendantsOption fetchDescendantsOption = Boolean.TRUE.equals(includeDescendants)
             ? FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS : FetchDescendantsOption.OMIT_DESCENDANTS;
-        final DataNode dataNode =
-            cpsDataService.getDataNode(dataspaceName, anchorName, xpath, fetchDescendantsOption);
+        final DataNode dataNode = cpsDataService.getDataNode(dataspaceName, anchorName, xpath,
+                fetchDescendantsOption);
         return new ResponseEntity<>(DataMapUtils.toDataMap(dataNode), HttpStatus.OK);
     }
 
index 713dda1..299299c 100755 (executable)
@@ -191,7 +191,7 @@ class DataRestControllerSpec extends Specification {
             'no dataspace'    | '/x-path' | new DataspaceNotFoundException('')               || HttpStatus.BAD_REQUEST
             'no anchor'       | '/x-path' | new AnchorNotFoundException('', '')              || HttpStatus.BAD_REQUEST
             'no data'         | '/x-path' | new DataNodeNotFoundException('', '', '')        || HttpStatus.NOT_FOUND
-            'empty path'      | ''        | new IllegalStateException()                      || HttpStatus.NOT_IMPLEMENTED
+            'root path'       | '/'       | new DataNodeNotFoundException('', '')            || HttpStatus.NOT_FOUND
             'already defined' | '/x-path' | new AlreadyDefinedException('', new Throwable()) || HttpStatus.CONFLICT
     }
 
old mode 100644 (file)
new mode 100755 (executable)
index fa13c7d..a02b193
@@ -130,7 +130,12 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
         final String xpath) {
         final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
         final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
-        return fragmentRepository.getByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity, xpath);
+        if (isRootXpath(xpath)) {
+            return fragmentRepository.getFirstByDataspaceAndAnchor(dataspaceEntity, anchorEntity);
+        } else {
+            return fragmentRepository.getByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity,
+                xpath);
+        }
     }
 
     @Override
@@ -205,4 +210,8 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
         fragmentEntity.setChildFragments(Collections.emptySet());
         fragmentRepository.save(fragmentEntity);
     }
+
+    private boolean isRootXpath(final String xpath) {
+        return "/".equals(xpath) || "".equals(xpath);
+    }
 }
index b896fe8..74d0461 100755 (executable)
@@ -48,6 +48,15 @@ public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>
             .orElseThrow(() -> new DataNodeNotFoundException(dataspaceEntity.getName(), anchorEntity.getName(), xpath));\r
     }\r
 \r
+    Optional<FragmentEntity> findFirstByDataspaceAndAnchor(@NonNull DataspaceEntity dataspaceEntity,\r
+        @NonNull AnchorEntity anchorEntity);\r
+\r
+    default FragmentEntity getFirstByDataspaceAndAnchor(@NonNull DataspaceEntity dataspaceEntity,\r
+        @NonNull AnchorEntity anchorEntity) {\r
+        return findFirstByDataspaceAndAnchor(dataspaceEntity, anchorEntity)\r
+            .orElseThrow(() -> new DataNodeNotFoundException(dataspaceEntity.getName(), anchorEntity.getName()));\r
+    }\r
+\r
     @Modifying\r
     @Query("DELETE FROM FragmentEntity fe WHERE fe.anchor IN (:anchors)")\r
     void deleteByAnchorIn(@NotNull @Param("anchors") Collection<AnchorEntity> anchorEntities);\r
old mode 100644 (file)
new mode 100755 (executable)
index a47bd65..ea6b269
@@ -167,23 +167,30 @@ class CpsDataPersistenceServiceSpec extends CpsPersistenceSpecBase {
         return fragmentRepository.findByDataspaceAndAnchorAndXpath(dataspace, anchor, xpath).orElseThrow()
     }
 
+    @Unroll
     @Sql([CLEAR_DATA, SET_DATA])
     def 'Get data node by xpath without descendants.'() {
         when: 'data node is requested'
             def result = objectUnderTest.getDataNode(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES,
-                    XPATH_DATA_NODE_WITH_LEAVES, OMIT_DESCENDANTS)
+                    inputXPath, OMIT_DESCENDANTS)
         then: 'data node is returned with no descendants'
             assert result.getXpath() == XPATH_DATA_NODE_WITH_LEAVES
         and: 'expected leaves'
             assert result.getChildDataNodes().size() == 0
             assertLeavesMaps(result.getLeaves(), expectedLeavesByXpathMap[XPATH_DATA_NODE_WITH_LEAVES])
+        where: 'the following data is used'
+            scenario                      | inputXPath
+            'some xpath'                  |'/parent-100'
+            'root xpath'                  |'/'
+            'empty xpath'                 |''
     }
 
+    @Unroll
     @Sql([CLEAR_DATA, SET_DATA])
     def 'Get data node by xpath with all descendants.'() {
         when: 'data node is requested with all descendants'
             def result = objectUnderTest.getDataNode(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES,
-                    XPATH_DATA_NODE_WITH_LEAVES, INCLUDE_ALL_DESCENDANTS)
+                    inputXPath, INCLUDE_ALL_DESCENDANTS)
             def mappedResult = treeToFlatMapByXpath(new HashMap<>(), result)
         then: 'data node is returned with all the descendants populated'
             assert mappedResult.size() == 4
@@ -192,9 +199,12 @@ class CpsDataPersistenceServiceSpec extends CpsPersistenceSpecBase {
             assert mappedResult.get('/parent-100/child-002').getChildDataNodes().size() == 1
         and: 'extracted leaves maps are matching expected'
             mappedResult.forEach(
-                    (xpath, dataNode) ->
-                            assertLeavesMaps(dataNode.getLeaves(), expectedLeavesByXpathMap[xpath])
-            )
+                    (inputXPath, dataNode) -> assertLeavesMaps(dataNode.getLeaves(), expectedLeavesByXpathMap[inputXPath]))
+        where: 'the following data is used'
+            scenario                      | inputXPath
+            'some xpath'                  |'/parent-100'
+            'root xpath'                  |'/'
+            'empty xpath'                 |''
     }
 
     def static assertLeavesMaps(actualLeavesMap, expectedLeavesMap) {
old mode 100644 (file)
new mode 100755 (executable)
index b6dc2ca..d252fbb
@@ -8,13 +8,13 @@ INSERT INTO ANCHOR (ID, NAME, DATASPACE_ID, SCHEMA_SET_ID) VALUES
     (3001, 'ANCHOR-001', 1001, 2001),
     (3003, 'ANCHOR-003', 1001, 2001);
 
-INSERT INTO FRAGMENT (ID, XPATH, ANCHOR_ID, PARENT_ID, DATASPACE_ID) VALUES
-    (4001, '/parent-1', 3001, null, 1001),
-    (4002, '/parent-2', 3001, null, 1001),
-    (4003, '/parent-3', 3001, null, 1001),
-    (4004, '/parent-1/child-1', 3001, 4001, 1001),
-    (4005, '/parent-2/child-2', 3001, 4002, 1001),
-    (4006, '/parent-1/child-1/grandchild-1', 3001, 4004, 1001);
+INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH) VALUES
+    (4001, 1001, 3001, null, '/parent-1'),
+    (4002, 1001, 3001, null, '/parent-2'),
+    (4003, 1001, 3001, null, '/parent-3'),
+    (4004, 1001, 3001, 4001, '/parent-1/child-1'),
+    (4005, 1001, 3001, 4002, '/parent-2/child-2'),
+    (4006, 1001, 3001, 4004, '/parent-1/child-1/grandchild-1');
 
 INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH, ATTRIBUTES) VALUES
     (4101, 1001, 3003, null, '/parent-100', '{"parent-leaf": "parent-leaf value"}'),
old mode 100644 (file)
new mode 100755 (executable)
index 125b93d..6e7ac3b
@@ -39,4 +39,15 @@ public class DataNodeNotFoundException extends DataValidationException {
             .format("DataNode with xpath %s was not found for anchor %s and dataspace %s.", xpath,
                 anchorName, dataspaceName));
     }
+
+    /**
+     * Constructor.
+     *
+     * @param dataspaceName the name of the dataspace
+     * @param anchorName the anchor name
+     */
+    public DataNodeNotFoundException(final String dataspaceName, final String anchorName) {
+        super("DataNode not found", String.format(
+            "DataNode not found for anchor %s and dataspace %s.", anchorName, dataspaceName));
+    }
 }
index d2f43c9..8592af9 100755 (executable)
@@ -119,12 +119,18 @@ class CpsExceptionsSpec extends Specification {
                     + "Anchor records associated.")
     }
 
-    def 'Creating a exception that a datanode does not exist.'() {
+    def 'Creating a exception that a datanode with a specified xpath does not exist.'() {
         expect: 'the exception details contains the correct message with dataspace name and xpath.'
             (new DataNodeNotFoundException(dataspaceName, anchorName, xpath)).details
                     == "DataNode with xpath ${xpath} was not found for anchor ${anchorName} and dataspace ${dataspaceName}."
     }
 
+    def 'Creating a exception that a datanode does not exist.'() {
+        expect: 'the exception details contains the correct message with dataspace name and anchor.'
+            (new DataNodeNotFoundException(dataspaceName, anchorName)).details
+                    == "DataNode not found for anchor ${anchorName} and dataspace ${dataspaceName}."
+    }
+
     def 'Creating a exception that a dataspace already exists.'() {
         expect: 'the exception details contains the correct message with dataspace name.'
             (AlreadyDefinedException.forDataspace(dataspaceName, rootCause)).details