diff options
author | 2023-04-25 11:58:35 +0530 | |
---|---|---|
committer | 2023-08-02 18:15:16 +0530 | |
commit | f248b5d9b794d5bdff59145406e0398d6fdcafa4 (patch) | |
tree | f99670f0911b4c6e5f13ec5590fe15eb205f0dc3 /cps-service/src | |
parent | 7fcffe5ae6753bfb6746d18d41ec536092603a76 (diff) |
Support pagination in query across all anchors(ep4)
Add pagination query parameters in query across all anchors API
pagination parameters (pageIndex and pageSize) are optional
default is to query all fragments
each pageSize represents number of records(number of anchors)
TotalRecords is returned in response header to find number of pages.
- If pagination option is provided in request then query number of
anchors equal to pageSize. pageIndex is used for setting offset.
- return number of records(one anchor per record) as per pagesize
and pageSize
Issue-ID: CPS-1605
Change-ID: I73f97f986a817d423f93a8d922dcd9647b2504bc
Signed-off-by: rajesh.kumar <rk00747546@techmahindra.com>
Diffstat (limited to 'cps-service/src')
9 files changed, 166 insertions, 23 deletions
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 af54077fe..edd2d2ad3 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 @@ -23,6 +23,7 @@ package org.onap.cps.api; import java.util.Collection; import org.onap.cps.spi.FetchDescendantsOption; +import org.onap.cps.spi.PaginationOption; import org.onap.cps.spi.model.DataNode; /* @@ -50,8 +51,18 @@ public interface CpsQueryService { * @param cpsPath CPS path * @param fetchDescendantsOption defines whether the descendants of the node(s) found by the query should be * included in the output + * @param paginationOption pagination option * @return a collection of data nodes */ Collection<DataNode> queryDataNodesAcrossAnchors(String dataspaceName, String cpsPath, - FetchDescendantsOption fetchDescendantsOption); + FetchDescendantsOption fetchDescendantsOption, + PaginationOption paginationOption); + + /** + * Query total number of anchors for given dataspace name and cps path. + * @param dataspaceName dataspace name + * @param cpsPath cps path + * @return total number of anchors for given dataspace name and cps path. + */ + Integer countAnchorsForDataspaceAndCpsPath(String dataspaceName, 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 index ac018c9e8..1d7a7ceeb 100644 --- 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 @@ -27,6 +27,7 @@ import lombok.RequiredArgsConstructor; import org.onap.cps.api.CpsQueryService; import org.onap.cps.spi.CpsDataPersistenceService; import org.onap.cps.spi.FetchDescendantsOption; +import org.onap.cps.spi.PaginationOption; import org.onap.cps.spi.model.DataNode; import org.onap.cps.spi.utils.CpsValidator; import org.springframework.stereotype.Service; @@ -49,8 +50,17 @@ public class CpsQueryServiceImpl implements CpsQueryService { @Override public Collection<DataNode> queryDataNodesAcrossAnchors(final String dataspaceName, - final String cpsPath, final FetchDescendantsOption fetchDescendantsOption) { + final String cpsPath, final FetchDescendantsOption fetchDescendantsOption, + final PaginationOption paginationOption) { + cpsValidator.validateNameCharacters(dataspaceName); + cpsValidator.validatePaginationOption(paginationOption); + return cpsDataPersistenceService.queryDataNodesAcrossAnchors(dataspaceName, cpsPath, + fetchDescendantsOption, paginationOption); + } + + @Override + public Integer countAnchorsForDataspaceAndCpsPath(final String dataspaceName, final String cpsPath) { cpsValidator.validateNameCharacters(dataspaceName); - return cpsDataPersistenceService.queryDataNodesAcrossAnchors(dataspaceName, cpsPath, fetchDescendantsOption); + return cpsDataPersistenceService.countAnchorsForDataspaceAndCpsPath(dataspaceName, cpsPath); } } diff --git a/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java b/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java index 9674bbe8c..1baca4ea7 100644 --- a/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java +++ b/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java @@ -200,11 +200,12 @@ public interface CpsDataPersistenceService { * @param cpsPath cps path * @param fetchDescendantsOption defines whether the descendants of the node(s) found by the query should be * included in the output + * @param paginationOption pagination option * @return the data nodes found i.e. 0 or more data nodes */ List<DataNode> queryDataNodesAcrossAnchors(String dataspaceName, - String cpsPath, FetchDescendantsOption fetchDescendantsOption); - + String cpsPath, FetchDescendantsOption fetchDescendantsOption, + PaginationOption paginationOption); /** * Starts a session which allows use of locks and batch interaction with the persistence service. @@ -230,4 +231,12 @@ public interface CpsDataPersistenceService { * @param timeoutInMilliseconds lock attempt timeout in milliseconds */ void lockAnchor(String sessionID, String dataspaceName, String anchorName, Long timeoutInMilliseconds); + + /** + * Query total anchors for dataspace name and cps path. + * @param dataspaceName datasoace name + * @param cpsPath cps path + * @return total anchors for dataspace name and cps path + */ + Integer countAnchorsForDataspaceAndCpsPath(String dataspaceName, String cpsPath); } diff --git a/cps-service/src/main/java/org/onap/cps/spi/PaginationOption.java b/cps-service/src/main/java/org/onap/cps/spi/PaginationOption.java new file mode 100644 index 000000000..17f025dba --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/spi/PaginationOption.java @@ -0,0 +1,38 @@ +/* + * ============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========================================================= + */ + +package org.onap.cps.spi; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class PaginationOption { + + private int pageIndex; + + private int pageSize; + + public static final PaginationOption NO_PAGINATION = null; + + public boolean isValidPaginationOption() { + return this.pageIndex > 0 && this.pageSize > 0; + } +} diff --git a/cps-service/src/main/java/org/onap/cps/spi/utils/CpsValidator.java b/cps-service/src/main/java/org/onap/cps/spi/utils/CpsValidator.java index 231094cf1..ceb75c09b 100644 --- a/cps-service/src/main/java/org/onap/cps/spi/utils/CpsValidator.java +++ b/cps-service/src/main/java/org/onap/cps/spi/utils/CpsValidator.java @@ -20,6 +20,8 @@ package org.onap.cps.spi.utils; +import org.onap.cps.spi.PaginationOption; + public interface CpsValidator { /** @@ -35,4 +37,11 @@ public interface CpsValidator { * @param names names of data to be validated */ void validateNameCharacters(final Iterable<String> names); + + /** + * Validate pagination option. + * + * @param paginationOption pagination option + */ + void validatePaginationOption(final PaginationOption paginationOption); } diff --git a/cps-service/src/main/java/org/onap/cps/utils/DataMapUtils.java b/cps-service/src/main/java/org/onap/cps/utils/DataMapUtils.java index b4d5a0944..1ac2bddf8 100644 --- a/cps-service/src/main/java/org/onap/cps/utils/DataMapUtils.java +++ b/cps-service/src/main/java/org/onap/cps/utils/DataMapUtils.java @@ -28,8 +28,10 @@ import static java.util.stream.Collectors.toUnmodifiableList; import static java.util.stream.Collectors.toUnmodifiableMap; import com.google.common.collect.ImmutableMap; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.Map; import lombok.AccessLevel; import lombok.NoArgsConstructor; @@ -52,17 +54,29 @@ public class DataMapUtils { } /** - * Converts DataNode structure into a map including the root node identifier for a JSON response. - * - * @param dataNode data node object - * @return a map representing same data with the root node identifier + * Converts list of DataNode structure into a map including the root node identifier for a JSON response. + * @param dataNodeList list of data nodes for a given anchor name + * @param anchorName anchor name + * @param prefix prefix + * @return a map representing same list of data for given anchor with the root node identifier */ - public static Map<String, Object> toDataMapWithIdentifierAndAnchor(final DataNode dataNode, final String prefix) { - final String nodeIdentifierWithPrefix = getNodeIdentifierWithPrefix(dataNode.getXpath(), prefix); - final Map<String, Object> dataMap = ImmutableMap.<String, Object>builder() - .put(nodeIdentifierWithPrefix, toDataMap(dataNode)).build(); - return ImmutableMap.<String, Object>builder().put("anchorName", dataNode.getAnchorName()) - .put("dataNode", dataMap).build(); + public static Map<String, Object> toDataMapWithIdentifierAndAnchor(final List<DataNode> dataNodeList, + final String anchorName, final String prefix) { + final List<Map<String, Object>> dataMaps = toDataNodesWithIdentifier(dataNodeList, prefix); + return ImmutableMap.<String, Object>builder().put("anchorName", anchorName) + .put("dataNodes", dataMaps).build(); + } + + private static List<Map<String, Object>> toDataNodesWithIdentifier(final List<DataNode> dataNodeList, + final String prefix) { + final List<Map<String, Object>> dataMaps = new ArrayList<>(dataNodeList.size()); + for (final DataNode dataNode: dataNodeList) { + final String nodeIdentifierWithPrefix = getNodeIdentifierWithPrefix(dataNode.getXpath(), prefix); + final Map<String, Object> dataMap = ImmutableMap.<String, Object>builder() + .put(nodeIdentifierWithPrefix, toDataMap(dataNode)).build(); + dataMaps.add(dataMap); + } + return dataMaps; } /** 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 index 553027a4b..1ad501791 100644 --- 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 @@ -23,6 +23,7 @@ package org.onap.cps.api.impl import org.onap.cps.spi.CpsDataPersistenceService import org.onap.cps.spi.FetchDescendantsOption +import org.onap.cps.spi.PaginationOption import org.onap.cps.spi.utils.CpsValidator import spock.lang.Specification @@ -52,14 +53,22 @@ class CpsQueryServiceImplSpec extends Specification { given: 'a dataspace name, an anchor name and a cps path' def dataspaceName = 'some-dataspace' def cpsPath = '/cps-path' + def paginationOption = new PaginationOption(1, 2) when: 'queryDataNodes is invoked' - objectUnderTest.queryDataNodesAcrossAnchors(dataspaceName, cpsPath, fetchDescendantsOption) + objectUnderTest.queryDataNodesAcrossAnchors(dataspaceName, cpsPath, fetchDescendantsOption, paginationOption) then: 'the persistence service is called once with the correct parameters' - 1 * mockCpsDataPersistenceService.queryDataNodesAcrossAnchors(dataspaceName, cpsPath, fetchDescendantsOption) + 1 * mockCpsDataPersistenceService.queryDataNodesAcrossAnchors(dataspaceName, cpsPath, fetchDescendantsOption, paginationOption) and: 'the CpsValidator is called on the dataspaceName, schemaSetName and anchorName' 1 * mockCpsValidator.validateNameCharacters(dataspaceName) where: 'all fetch descendants options are supported' - fetchDescendantsOption << [FetchDescendantsOption.OMIT_DESCENDANTS, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS] + fetchDescendantsOption << [FetchDescendantsOption.OMIT_DESCENDANTS, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS, + FetchDescendantsOption.DIRECT_CHILDREN_ONLY, new FetchDescendantsOption(10)] } + def 'Query total anchors for dataspace and cps path.'() { + when: 'query total anchors is invoked' + objectUnderTest.countAnchorsForDataspaceAndCpsPath("some-dataspace", "/cps-path") + then: 'the persistence service is called once with the correct parameters' + 1 * mockCpsDataPersistenceService.countAnchorsForDataspaceAndCpsPath("some-dataspace", "/cps-path") + } } diff --git a/cps-service/src/test/groovy/org/onap/cps/spi/PaginationOptionSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/spi/PaginationOptionSpec.groovy new file mode 100644 index 000000000..9d74a1722 --- /dev/null +++ b/cps-service/src/test/groovy/org/onap/cps/spi/PaginationOptionSpec.groovy @@ -0,0 +1,41 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2022-2023 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. + * 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 PaginationOptionSpec extends Specification { + + def 'Pagination validation with: #scenario'() { + given: 'pagination option with pageIndex and pageSize' + def paginationOption = new PaginationOption(pageIndex, pageSize) + expect: 'validation returns expected result' + assert paginationOption.isValidPaginationOption() == expectedIsValidPaginationOption + where: 'following parameters are used' + scenario | pageIndex | pageSize || expectedIsValidPaginationOption + 'valid pagination' | 1 | 1 || true + 'negative index' | -1 | 1 || false + 'negative size' | 1 | -1 || false + 'zero index' | 0 | 1 || false + 'zero size' | 1 | 0 || false + } +} diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/DataMapUtilsSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/DataMapUtilsSpec.groovy index 29085a9c7..6b9f9acb3 100644 --- a/cps-service/src/test/groovy/org/onap/cps/utils/DataMapUtilsSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/utils/DataMapUtilsSpec.groovy @@ -74,17 +74,19 @@ class DataMapUtilsSpec extends Specification { def 'Data node structure with anchor name conversion to map with root node identifier.'() { when: 'data node structure is converted to a map with root node identifier' - def result = DataMapUtils.toDataMapWithIdentifierAndAnchor(dataNodeWithAnchor, dataNodeWithAnchor.moduleNamePrefix) + def result = DataMapUtils.toDataMapWithIdentifierAndAnchor([dataNodeWithAnchor], dataNodeWithAnchor.anchorName, dataNodeWithAnchor.moduleNamePrefix) then: 'root node leaves are populated under its node identifier' - def parentNode = result.get("dataNode").parent - parentNode.parentLeaf == 'parentLeafValue' - parentNode.parentLeafList == ['parentLeafListEntry1','parentLeafListEntry2'] + def dataNodes = result.dataNodes as List + assert dataNodes.size() == 1 + def parentNode = dataNodes[0].parent + assert parentNode.parentLeaf == 'parentLeafValue' + assert parentNode.parentLeafList == ['parentLeafListEntry1','parentLeafListEntry2'] and: 'leaves for child element is populated under its node identifier' assert parentNode.'child-object'.childLeaf == 'childLeafValue' and: 'leaves for grandchild element is populated under its node identifier' assert parentNode.'child-object'.'grand-child-object'.grandChildLeaf == 'grandChildLeafValue' and: 'data node is associated with anchor name' - assert result.get('anchorName') == 'anchor01' + assert result.anchorName == 'anchor01' } def 'Data node without leaves and without children.'() { |