Fix Id-searches endpoint performance degradation 46/131146/9
authorkissand <andras.zoltan.kiss@est.tech>
Fri, 23 Sep 2022 11:49:05 +0000 (13:49 +0200)
committerAndras Zoltan Kiss <andras.zoltan.kiss@est.tech>
Wed, 12 Oct 2022 12:32:42 +0000 (12:32 +0000)
- create more flexible control over fetch descendants
- add a new FETCH_DIRECT_CHILDREN_ONLY option to fetch descendants
options
- enabel create custom fetch descendants option

Reviewer: Toine, Joe, Priyank
Issue-ID: CPS-1216
Change-Id: I900b32e813367aa9566c1dec986b20f009d27203
Signed-off-by: kissand <andras.zoltan.kiss@est.tech>
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceImpl.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImpl.java
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceSpec.groovy
cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java
cps-service/src/main/java/org/onap/cps/spi/FetchDescendantsOption.java
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsQueryServiceImplSpec.groovy
cps-service/src/test/groovy/org/onap/cps/spi/FetchDescendantsOptionSpec.groovy [new file with mode: 0644]

index f8836e6..1674c52 100644 (file)
@@ -21,6 +21,7 @@
 package org.onap.cps.ncmp.api.impl;
 
 import static org.onap.cps.ncmp.api.impl.utils.YangDataConverter.convertYangModelCmHandleToNcmpServiceCmHandle;
+import static org.onap.cps.spi.FetchDescendantsOption.FETCH_DIRECT_CHILDREN_ONLY;
 import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS;
 import static org.onap.cps.utils.CmHandleQueryRestParametersValidator.validateCpsPathConditionProperties;
 import static org.onap.cps.utils.CmHandleQueryRestParametersValidator.validateModuleNameConditionProperties;
@@ -68,14 +69,14 @@ public class NetworkCmProxyCmHandlerQueryServiceImpl implements NetworkCmProxyCm
      */
     @Override
     public Set<NcmpServiceCmHandle> queryCmHandles(
-        final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) {
+            final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) {
 
         if (cmHandleQueryServiceParameters.getCmHandleQueryParameters().isEmpty()) {
             return getAllCmHandles();
         }
 
         final Map<String, NcmpServiceCmHandle> combinedQueryResult = executeInventoryQueries(
-            cmHandleQueryServiceParameters);
+                cmHandleQueryServiceParameters);
 
         return new HashSet<>(combineWithModuleNameQuery(cmHandleQueryServiceParameters, combinedQueryResult).values());
     }
@@ -88,17 +89,17 @@ public class NetworkCmProxyCmHandlerQueryServiceImpl implements NetworkCmProxyCm
      */
     @Override
     public Set<String> queryCmHandleIds(
-        final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) {
+            final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) {
 
         if (cmHandleQueryServiceParameters.getCmHandleQueryParameters().isEmpty()) {
             return getAllCmHandleIds();
         }
 
         final Map<String, NcmpServiceCmHandle> combinedQueryResult = executeInventoryQueries(
-            cmHandleQueryServiceParameters);
+                cmHandleQueryServiceParameters);
 
         final Collection<String> moduleNamesForQuery =
-            getModuleNamesForQuery(cmHandleQueryServiceParameters.getCmHandleQueryParameters());
+                getModuleNamesForQuery(cmHandleQueryServiceParameters.getCmHandleQueryParameters());
         if (moduleNamesForQuery.isEmpty()) {
             return combinedQueryResult.keySet();
         }
@@ -113,10 +114,10 @@ public class NetworkCmProxyCmHandlerQueryServiceImpl implements NetworkCmProxyCm
     }
 
     private Map<String, NcmpServiceCmHandle> combineWithModuleNameQuery(
-        final CmHandleQueryServiceParameters cmHandleQueryServiceParameters,
-        final Map<String, NcmpServiceCmHandle> previousQueryResult) {
+            final CmHandleQueryServiceParameters cmHandleQueryServiceParameters,
+            final Map<String, NcmpServiceCmHandle> previousQueryResult) {
         final Collection<String> moduleNamesForQuery =
-            getModuleNamesForQuery(cmHandleQueryServiceParameters.getCmHandleQueryParameters());
+                getModuleNamesForQuery(cmHandleQueryServiceParameters.getCmHandleQueryParameters());
         if (moduleNamesForQuery.isEmpty()) {
             return previousQueryResult;
         }
@@ -138,7 +139,7 @@ public class NetworkCmProxyCmHandlerQueryServiceImpl implements NetworkCmProxyCm
     }
 
     private Map<String, NcmpServiceCmHandle> executeInventoryQueries(
-        final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) {
+            final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) {
         final Map<String, String> cpsPath = getCpsPath(cmHandleQueryServiceParameters.getCmHandleQueryParameters());
         if (!validateCpsPathConditionProperties(cpsPath)) {
             return Collections.emptyMap();
@@ -149,13 +150,13 @@ public class NetworkCmProxyCmHandlerQueryServiceImpl implements NetworkCmProxyCm
         } else {
             try {
                 cpsPathQueryResult = cmHandleQueries.queryCmHandleDataNodesByCpsPath(
-                    cpsPath.get("cpsPath"), INCLUDE_ALL_DESCENDANTS)
-                    .stream().map(this::createNcmpServiceCmHandle)
-                    .collect(Collectors.toMap(NcmpServiceCmHandle::getCmHandleId,
-                        Function.identity()));
+                                cpsPath.get("cpsPath"), INCLUDE_ALL_DESCENDANTS)
+                        .stream().map(this::createNcmpServiceCmHandle)
+                        .collect(Collectors.toMap(NcmpServiceCmHandle::getCmHandleId,
+                                Function.identity()));
             } catch (final PathParsingException pathParsingException) {
                 throw new DataValidationException(pathParsingException.getMessage(), pathParsingException.getDetails(),
-                    pathParsingException);
+                        pathParsingException);
             }
             if (cpsPathQueryResult.isEmpty()) {
                 return Collections.emptyMap();
@@ -163,9 +164,9 @@ public class NetworkCmProxyCmHandlerQueryServiceImpl implements NetworkCmProxyCm
         }
 
         final Map<String, String> publicPropertyQueryPairs =
-            getPublicPropertyPairs(cmHandleQueryServiceParameters.getCmHandleQueryParameters());
+                getPublicPropertyPairs(cmHandleQueryServiceParameters.getCmHandleQueryParameters());
         final Map<String, NcmpServiceCmHandle> propertiesQueryResult = publicPropertyQueryPairs.isEmpty()
-            ? NO_QUERY_TO_EXECUTE : cmHandleQueries.queryCmHandlePublicProperties(publicPropertyQueryPairs);
+                ? NO_QUERY_TO_EXECUTE : cmHandleQueries.queryCmHandlePublicProperties(publicPropertyQueryPairs);
 
         return cmHandleQueries.combineCmHandleQueries(cpsPathQueryResult, propertiesQueryResult);
     }
@@ -190,14 +191,14 @@ public class NetworkCmProxyCmHandlerQueryServiceImpl implements NetworkCmProxyCm
     private Map<String, String> getCpsPath(final List<ConditionProperties> conditionProperties) {
         final Map<String, String> result = new HashMap<>();
         getConditions(conditionProperties, ValidQueryProperties.WITH_CPS_PATH.getQueryProperty()).forEach(
-            result::putAll);
+                result::putAll);
         return result;
     }
 
     private Map<String, String> getPublicPropertyPairs(final List<ConditionProperties> conditionProperties) {
         final Map<String, String> result = new HashMap<>();
         getConditions(conditionProperties,
-            ValidQueryProperties.HAS_ALL_PROPERTIES.getQueryProperty()).forEach(result::putAll);
+                ValidQueryProperties.HAS_ALL_PROPERTIES.getQueryProperty()).forEach(result::putAll);
         return result;
     }
 
@@ -213,17 +214,17 @@ public class NetworkCmProxyCmHandlerQueryServiceImpl implements NetworkCmProxyCm
 
     private Set<NcmpServiceCmHandle> getAllCmHandles() {
         return inventoryPersistence.getDataNode("/dmi-registry")
-            .getChildDataNodes().stream().map(this::createNcmpServiceCmHandle).collect(Collectors.toSet());
+                .getChildDataNodes().stream().map(this::createNcmpServiceCmHandle).collect(Collectors.toSet());
     }
 
     private Set<String> getAllCmHandleIds() {
-        return inventoryPersistence.getDataNode("/dmi-registry")
-            .getChildDataNodes().stream().map(dataNode -> dataNode.getLeaves().get("id").toString())
-            .collect(Collectors.toSet());
+        return inventoryPersistence.getDataNode("/dmi-registry", FETCH_DIRECT_CHILDREN_ONLY)
+                .getChildDataNodes().stream().map(dataNode -> dataNode.getLeaves().get("id").toString())
+                .collect(Collectors.toSet());
     }
 
     private NcmpServiceCmHandle createNcmpServiceCmHandle(final DataNode dataNode) {
         return convertYangModelCmHandleToNcmpServiceCmHandle(YangDataConverter
-            .convertCmHandleToYangModel(dataNode, dataNode.getLeaves().get("id").toString()));
+                .convertCmHandleToYangModel(dataNode, dataNode.getLeaves().get("id").toString()));
     }
 }
index bfc3a9a..b29825e 100644 (file)
@@ -23,6 +23,7 @@ package org.onap.cps.ncmp.api.inventory;
 import java.util.Collection;
 import java.util.Map;
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
+import org.onap.cps.spi.FetchDescendantsOption;
 import org.onap.cps.spi.model.Anchor;
 import org.onap.cps.spi.model.DataNode;
 import org.onap.cps.spi.model.ModuleDefinition;
@@ -113,6 +114,15 @@ public interface InventoryPersistence {
      */
     DataNode getDataNode(String xpath);
 
+    /**
+     * Get data node via xpath.
+     *
+     * @param xpath xpath
+     * @param fetchDescendantsOption fetch descendants option
+     * @return data node
+     */
+    DataNode getDataNode(String xpath, FetchDescendantsOption fetchDescendantsOption);
+
     /**
      * Get data node of given cm handle.
      *
index 99edfdb..eed47ed 100644 (file)
@@ -76,18 +76,18 @@ public class InventoryPersistenceImpl implements InventoryPersistence {
     @Override
     public CompositeState getCmHandleState(final String cmHandleId) {
         final DataNode stateAsDataNode = cpsDataService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-            String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId) + "/state",
-            FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS);
+                String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId) + "/state",
+                FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS);
         return new CompositeStateBuilder().fromDataNode(stateAsDataNode).build();
     }
 
     @Override
     public void saveCmHandleState(final String cmHandleId, final CompositeState compositeState) {
         final String cmHandleJsonData = String.format("{\"state\":%s}",
-            jsonObjectMapper.asJsonString(compositeState));
+                jsonObjectMapper.asJsonString(compositeState));
         cpsDataService.updateDataNodeAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-            String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId),
-            cmHandleJsonData, OffsetDateTime.now());
+                String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId),
+                cmHandleJsonData, OffsetDateTime.now());
     }
 
     @Override
@@ -153,8 +153,13 @@ public class InventoryPersistenceImpl implements InventoryPersistence {
 
     @Override
     public DataNode getDataNode(final String xpath) {
+        return getDataNode(xpath, INCLUDE_ALL_DESCENDANTS);
+    }
+
+    @Override
+    public DataNode getDataNode(final String xpath, final FetchDescendantsOption fetchDescendantsOption) {
         return cpsDataPersistenceService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-                xpath, INCLUDE_ALL_DESCENDANTS);
+                xpath, fetchDescendantsOption);
     }
 
     @Override
@@ -164,7 +169,7 @@ public class InventoryPersistenceImpl implements InventoryPersistence {
 
     @Override
     public Collection<Anchor> queryAnchors(final Collection<String> moduleNamesForQuery) {
-        return  cpsAdminPersistenceService.queryAnchors(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, moduleNamesForQuery);
+        return cpsAdminPersistenceService.queryAnchors(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, moduleNamesForQuery);
     }
 
     @Override
index f76316f..eea53e8 100644 (file)
@@ -153,7 +153,9 @@ class NetworkCmProxyCmHandlerQueryServiceSpec extends Specification {
     def 'Retrieve cm handles when the query is empty.'() {
         given: 'We use an empty query'
             def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
-        and: 'the inventory persistence returns the dmi registry datanode'
+        and: 'the inventory persistence returns the dmi registry datanode with just ids'
+            inventoryPersistence.getDataNode("/dmi-registry", FetchDescendantsOption.FETCH_DIRECT_CHILDREN_ONLY) >> dmiRegistry
+        and: 'the inventory persistence returns the dmi registry datanode with data'
             inventoryPersistence.getDataNode("/dmi-registry") >> dmiRegistry
         when: 'the query is executed for both cm handle ids and details'
             def returnedCmHandlesJustIds = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters)
index c13422d..ebc851a 100644 (file)
@@ -22,8 +22,6 @@
 
 package org.onap.cps.spi.impl;
 
-import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS;
-
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSet.Builder;
 import java.util.ArrayList;
@@ -102,7 +100,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
 
     @Override
     public void addMultipleLists(final String dataspaceName, final String anchorName, final String parentNodeXpath,
-            final Collection<Collection<DataNode>> newLists) {
+                                 final Collection<Collection<DataNode>> newLists) {
         final Collection<String> failedXpaths = new HashSet<>();
         newLists.forEach(newList -> {
             try {
@@ -119,7 +117,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
     }
 
     private void addNewChildDataNode(final String dataspaceName, final String anchorName,
-            final String parentNodeXpath, final DataNode newChild) {
+                                     final String parentNodeXpath, final DataNode newChild) {
         final FragmentEntity parentFragmentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
         final FragmentEntity newChildAsFragmentEntity =
                 convertToFragmentWithAllDescendants(parentFragmentEntity.getDataspace(),
@@ -134,7 +132,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
     }
 
     private void addChildrenDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath,
-            final Collection<DataNode> newChildren) {
+                                      final Collection<DataNode> newChildren) {
         final FragmentEntity parentFragmentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
         final List<FragmentEntity> fragmentEntities = new ArrayList<>(newChildren.size());
         try {
@@ -154,7 +152,8 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
     }
 
     private void retrySavingEachChildIndividually(final String dataspaceName, final String anchorName,
-            final String parentNodeXpath, final Collection<DataNode> newChildren) {
+                                                  final String parentNodeXpath,
+                                                  final Collection<DataNode> newChildren) {
         final Collection<String> failedXpaths = new HashSet<>();
         for (final DataNode newChild : newChildren) {
             try {
@@ -191,7 +190,8 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
      * @return a Fragment built from current DataNode
      */
     private FragmentEntity convertToFragmentWithAllDescendants(final DataspaceEntity dataspaceEntity,
-                             final AnchorEntity anchorEntity, final DataNode dataNodeToBeConverted) {
+                                                               final AnchorEntity anchorEntity,
+                                                               final DataNode dataNodeToBeConverted) {
         final FragmentEntity parentFragment = toFragmentEntity(dataspaceEntity, anchorEntity, dataNodeToBeConverted);
         final Builder<FragmentEntity> childFragmentsImmutableSetBuilder = ImmutableSet.builder();
         for (final DataNode childDataNode : dataNodeToBeConverted.getChildDataNodes()) {
@@ -226,7 +226,8 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
         final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
         final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
         if (isRootXpath(xpath)) {
-            return fragmentRepository.findFirstRootByDataspaceAndAnchor(dataspaceEntity, anchorEntity);
+            return fragmentRepository.findFirstRootByDataspaceAndAnchor(
+                    dataspaceEntity, anchorEntity);
         } else {
             final String normalizedXpath;
             try {
@@ -235,7 +236,8 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
                 throw new CpsPathException(e.getMessage());
             }
 
-            return fragmentRepository.getByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity, normalizedXpath);
+            return fragmentRepository.getByDataspaceAndAnchorAndXpath(
+                    dataspaceEntity, anchorEntity, normalizedXpath);
         }
     }
 
@@ -319,10 +321,10 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
 
     private List<DataNode> getChildDataNodes(final FragmentEntity fragmentEntity,
                                              final FetchDescendantsOption fetchDescendantsOption) {
-        if (fetchDescendantsOption == INCLUDE_ALL_DESCENDANTS) {
+        if (fetchDescendantsOption.hasNext()) {
             return fragmentEntity.getChildFragments().stream()
-                    .map(childFragmentEntity -> toDataNode(childFragmentEntity, fetchDescendantsOption))
-                    .collect(Collectors.toUnmodifiableList());
+                    .map(childFragmentEntity -> toDataNode(childFragmentEntity, fetchDescendantsOption.next()))
+                    .collect(Collectors.toList());
         }
         return Collections.emptyList();
     }
@@ -355,10 +357,11 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
                                               final List<DataNode> dataNodes) {
 
         final Map<DataNode, FragmentEntity> dataNodeFragmentEntityMap = dataNodes.stream()
-            .collect(Collectors.toMap(
-                dataNode -> dataNode, dataNode -> getFragmentByXpath(dataspaceName, anchorName, dataNode.getXpath())));
+                .collect(Collectors.toMap(
+                        dataNode -> dataNode,
+                        dataNode -> getFragmentByXpath(dataspaceName, anchorName, dataNode.getXpath())));
         dataNodeFragmentEntityMap.forEach(
-            (dataNode, fragmentEntity) -> updateFragmentEntityAndDescendantsWithDataNode(fragmentEntity, dataNode));
+                (dataNode, fragmentEntity) -> updateFragmentEntityAndDescendantsWithDataNode(fragmentEntity, dataNode));
         try {
             fragmentRepository.saveAll(dataNodeFragmentEntityMap.values());
         } catch (final StaleStateException staleStateException) {
@@ -367,7 +370,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
     }
 
     private void retryUpdateDataNodesIndividually(final String dataspaceName, final String anchorName,
-            final Collection<FragmentEntity> fragmentEntities) {
+                                                  final Collection<FragmentEntity> fragmentEntities) {
         final Collection<String> failedXpaths = new HashSet<>();
 
         fragmentEntities.forEach(dataNodeFragment -> {
index 0c994d8..b80054a 100644 (file)
@@ -1,6 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2021 Pantheon.tech
+ *  Copyright (C) 2022 Nordix Foundation
  *  ================================================================================
  *  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;
 
-public enum FetchDescendantsOption {
-    OMIT_DESCENDANTS,
-    INCLUDE_ALL_DESCENDANTS
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public class FetchDescendantsOption {
+
+    public static final FetchDescendantsOption FETCH_DIRECT_CHILDREN_ONLY = new FetchDescendantsOption(1);
+    public static final FetchDescendantsOption OMIT_DESCENDANTS = new FetchDescendantsOption(0);
+    public static final FetchDescendantsOption INCLUDE_ALL_DESCENDANTS = new FetchDescendantsOption(-1);
+
+    private final int depth;
+
+    /**
+     * Has next depth.
+     *
+     * @return true if next level of depth is available
+     * @throws IllegalArgumentException when depth less than -1
+     */
+    public boolean hasNext() {
+        validateDepth(depth);
+        return depth > 0 || this.depth == INCLUDE_ALL_DESCENDANTS.depth;
+    }
+
+    /**
+     * Next fetch descendants option.
+     *
+     * @return the next fetch descendants option
+     * @throws IllegalArgumentException when depth less than -1 or 0
+     */
+    public FetchDescendantsOption next() {
+        if (depth == 0) {
+            throw new IllegalArgumentException("Do not use next() method with zero depth");
+        }
+        final FetchDescendantsOption nextDescendantsOption = this.depth == INCLUDE_ALL_DESCENDANTS.depth
+                ? INCLUDE_ALL_DESCENDANTS : new FetchDescendantsOption(depth - 1);
+        validateDepth(nextDescendantsOption.depth);
+        return nextDescendantsOption;
+    }
+
+    private static void validateDepth(final int depth) {
+        if (depth < -1) {
+            throw new IllegalArgumentException("A depth of less than minus one is not allowed");
+        }
+    }
+
 }
index 3f28f0a..a53706a 100644 (file)
@@ -188,7 +188,7 @@ class CpsDataServiceImplSpec extends Specification {
         expect: 'service returns same data if uses same parameters'
             objectUnderTest.getDataNode(dataspaceName, anchorName, xpath, fetchDescendantsOption) == dataNode
         where: 'all fetch options are supported'
-            fetchDescendantsOption << FetchDescendantsOption.values()
+            fetchDescendantsOption << [FetchDescendantsOption.OMIT_DESCENDANTS, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS]
     }
 
     def 'Get data node with option invalid #scenario.'() {
index 55a252c..b7fec85 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2021 Nordix Foundation
+ *  Copyright (C) 2021-2022 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -44,7 +44,7 @@ class CpsQueryServiceImplSpec extends Specification {
         then: 'the persistence service is called once with the correct parameters'
             1 * mockCpsDataPersistenceService.queryDataNodes(dataspaceName, anchorName, cpsPath, fetchDescendantsOption)
         where: 'all fetch descendants options are supported'
-            fetchDescendantsOption << FetchDescendantsOption.values()
+            fetchDescendantsOption << [FetchDescendantsOption.OMIT_DESCENDANTS, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS]
     }
 
     def 'Query data nodes by cps path with invalid #scenario.'() {
diff --git a/cps-service/src/test/groovy/org/onap/cps/spi/FetchDescendantsOptionSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/spi/FetchDescendantsOptionSpec.groovy
new file mode 100644 (file)
index 0000000..6273835
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2022 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.spi
+
+
+import spock.lang.Specification
+
+class FetchDescendantsOptionSpec extends Specification {
+    def 'Check has next descendant for fetch descendant option: #scenario'() {
+        when: 'fetch descendant option with #depth depth'
+            def fetchDescendantsOption = new FetchDescendantsOption(depth)
+        then: 'next level descendants available: #expectedHasNext'
+            fetchDescendantsOption.hasNext() == expectedHasNext
+        where: 'following parameters are used'
+            scenario                  | depth || expectedHasNext
+            'omit descendants'        | 0     || false
+            'first child'             | 1     || true
+            'second child'            | 2     || true
+            'include all descendants' | -1    || true
+    }
+
+    def 'Check has next descendant for fetch descendant option: invalid depth'() {
+        given: 'fetch descendant option with -2 depth'
+            def fetchDescendantsOption = new FetchDescendantsOption(-2)
+        when: 'next level descendants not available'
+            fetchDescendantsOption.hasNext()
+        then: 'exception thrown'
+            thrown IllegalArgumentException
+    }
+
+    def 'Get next descendant for fetch descendant option: #scenario'() {
+        when: 'fetch descendant option with #depth depth'
+            def fetchDescendantsOption = new FetchDescendantsOption(depth)
+        then: 'the next level of depth is as expected'
+            fetchDescendantsOption.next().depth == depth - 1
+        where: 'following parameters are used'
+            scenario                  | depth
+            'first child'             | 1
+            'second child'            | 2
+    }
+
+    def 'Get next descendant for fetch descendant option: include all descendants'() {
+        when: 'fetch descendant option with -1 depth'
+            def fetchDescendantsOption = new FetchDescendantsOption(-1)
+        then: 'the next level of depth is as expected'
+            fetchDescendantsOption.next().depth == -1
+    }
+
+    def 'Get next descendant for fetch descendant option: omit descendants'() {
+        given: 'fetch descendant option with 0 depth'
+            def fetchDescendantsOption = new FetchDescendantsOption(0)
+        when: 'the next level of depth is not allowed'
+            fetchDescendantsOption.next()
+        then: 'exception thrown'
+            thrown IllegalArgumentException
+    }
+}