Merge "Update CPSarchitecture release notes"
authorLuke Gleeson <luke.gleeson@est.tech>
Wed, 8 Mar 2023 09:45:45 +0000 (09:45 +0000)
committerGerrit Code Review <gerrit@onap.org>
Wed, 8 Mar 2023 09:45:45 +0000 (09:45 +0000)
87 files changed:
checkstyle/pom.xml
cps-application/pom.xml
cps-bom/pom.xml
cps-dependencies/pom.xml
cps-events/pom.xml
cps-ncmp-events/pom.xml
cps-ncmp-rest-stub/pom.xml
cps-ncmp-rest/pom.xml
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryController.java
cps-ncmp-service/pom.xml
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyCmHandleQueryService.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyCmHandlerQueryService.java with 78% similarity]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandleQueryServiceImpl.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceImpl.java [deleted file]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CmHandleQueries.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CmHandleQueriesImpl.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/NetworkCmProxyCmHandleQueryServiceSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceSpec.groovy [deleted file]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CmHandleQueriesImplSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImplSpec.groovy
cps-parent/pom.xml
cps-path-parser/pom.xml
cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java
cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathUtil.java
cps-rest/docs/openapi/cpsDataV2.yml
cps-rest/pom.xml
cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java
cps-rest/src/main/java/org/onap/cps/rest/utils/ZipFileSizeValidator.java
cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
cps-rest/src/test/groovy/org/onap/cps/rest/utils/MultipartFileUtilSpec.groovy
cps-rest/src/test/groovy/org/onap/cps/rest/utils/ZipFileSizeValidatorSpec.groovy
cps-ri/pom.xml
cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntity.java
cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java
cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java
cps-ri/src/main/java/org/onap/cps/spi/repository/AnchorRepository.java
cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentNativeRepositoryImpl.java
cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java
cps-ri/src/main/java/org/onap/cps/spi/repository/TempTableCreator.java
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistencePerfSpecBase.groovy
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistenceSpecBase.groovy
cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServiceDeletePerfTest.groovy
cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServicePerfTest.groovy
cps-ri/src/test/resources/data/anchor.sql
cps-ri/src/test/resources/data/fragment.sql
cps-service/pom.xml
cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java
cps-service/src/main/java/org/onap/cps/api/CpsDataService.java
cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java
cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java
cps-service/src/main/java/org/onap/cps/notification/CpsDataUpdatedEventFactory.java
cps-service/src/main/java/org/onap/cps/spi/CpsAdminPersistenceService.java
cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java
cps-service/src/main/java/org/onap/cps/spi/FetchDescendantsOption.java
cps-service/src/main/java/org/onap/cps/spi/exceptions/DataNodeNotFoundExceptionBatch.java [new file with mode: 0644]
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsQueryServiceImplSpec.groovy
cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdateEventFactorySpec.groovy
cps-service/src/test/groovy/org/onap/cps/spi/FetchDescendantsOptionSpec.groovy
docs/release-notes.rst
integration-test/pom.xml
integration-test/src/test/groovy/org/onap/cps/integration/CpsPersistenceSpec.groovy [deleted file]
integration-test/src/test/groovy/org/onap/cps/integration/base/BookstoreSpecBase.groovy [new file with mode: 0644]
integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy [moved from integration-test/src/test/groovy/org/onap/cps/integration/CpsIntegrationSpecBase.groovy with 54% similarity]
integration-test/src/test/groovy/org/onap/cps/integration/base/TestConfig.groovy [moved from integration-test/src/test/groovy/org/onap/cps/integration/TestConfig.groovy with 99% similarity]
integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsAdminServiceIntegrationSpec.groovy [new file with mode: 0644]
integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy [new file with mode: 0644]
jacoco-report/pom.xml
pom.xml
releases/3.2.3-container.yaml [new file with mode: 0644]
releases/3.2.3.yaml [new file with mode: 0644]
spotbugs/pom.xml
version.properties

index 9aea19e..74aa64d 100644 (file)
@@ -26,7 +26,7 @@
     <modelVersion>4.0.0</modelVersion>
     <groupId>org.onap.cps</groupId>
     <artifactId>checkstyle</artifactId>
-    <version>3.2.3-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
 
     <profiles>
         <profile>
index 1a4e2d1..00bf421 100755 (executable)
@@ -28,7 +28,7 @@
     <parent>
         <groupId>org.onap.cps</groupId>
         <artifactId>cps-parent</artifactId>
-        <version>3.2.3-SNAPSHOT</version>
+        <version>3.2.4-SNAPSHOT</version>
         <relativePath>../cps-parent/pom.xml</relativePath>
     </parent>
 
index b7046d3..23ac27b 100644 (file)
@@ -25,7 +25,7 @@
     <modelVersion>4.0.0</modelVersion>
     <groupId>org.onap.cps</groupId>
     <artifactId>cps-bom</artifactId>
-    <version>3.2.3-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
     <packaging>pom</packaging>
 
     <description>This artifact contains dependencyManagement declarations of all published CPS components.</description>
index c215990..5d29fbf 100755 (executable)
@@ -27,7 +27,7 @@
     <modelVersion>4.0.0</modelVersion>
     <groupId>org.onap.cps</groupId>
     <artifactId>cps-dependencies</artifactId>
-    <version>3.2.3-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
     <packaging>pom</packaging>
 
     <name>${project.groupId}:${project.artifactId}</name>
index ce8a959..2bfcbeb 100644 (file)
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.onap.cps</groupId>
         <artifactId>cps-parent</artifactId>
-        <version>3.2.3-SNAPSHOT</version>
+        <version>3.2.4-SNAPSHOT</version>
         <relativePath>../cps-parent/pom.xml</relativePath>
     </parent>
 
index 0b0c7f6..4d6e880 100644 (file)
@@ -23,7 +23,7 @@
     <parent>
         <groupId>org.onap.cps</groupId>
         <artifactId>cps-parent</artifactId>
-        <version>3.2.3-SNAPSHOT</version>
+        <version>3.2.4-SNAPSHOT</version>
         <relativePath>../cps-parent/pom.xml</relativePath>
     </parent>
 
index b1b6506..42c7ff4 100644 (file)
@@ -26,7 +26,7 @@
     <parent>
         <groupId>org.onap.cps</groupId>
         <artifactId>cps-parent</artifactId>
-        <version>3.2.3-SNAPSHOT</version>
+        <version>3.2.4-SNAPSHOT</version>
         <relativePath>../cps-parent/pom.xml</relativePath>
     </parent>
 
index ffbf617..f6e5580 100644 (file)
@@ -27,7 +27,7 @@
     <parent>
         <groupId>org.onap.cps</groupId>
         <artifactId>cps-parent</artifactId>
-        <version>3.2.3-SNAPSHOT</version>
+        <version>3.2.4-SNAPSHOT</version>
         <relativePath>../cps-parent/pom.xml</relativePath>
     </parent>
 
index 9f171f6..4da251f 100755 (executable)
@@ -1,7 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2021 Pantheon.tech
- *  Modifications Copyright (C) 2021-2022 Nordix Foundation
+ *  Modifications Copyright (C) 2021-2023 Nordix Foundation
  *  Modifications Copyright (C) 2021 highstreet technologies GmbH
  *  Modifications Copyright (C) 2021-2022 Bell Canada
  *  ================================================================================
@@ -28,8 +28,8 @@ import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum
 import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.PATCH;
 import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.UPDATE;
 
+import java.util.Collection;
 import java.util.List;
-import java.util.Set;
 import java.util.stream.Collectors;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -235,7 +235,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
             final CmHandleQueryParameters cmHandleQueryParameters) {
         final CmHandleQueryApiParameters cmHandleQueryApiParameters =
                 deprecationHelper.mapOldConditionProperties(cmHandleQueryParameters);
-        final Set<NcmpServiceCmHandle> cmHandles = networkCmProxyDataService
+        final Collection<NcmpServiceCmHandle> cmHandles = networkCmProxyDataService
                 .executeCmHandleSearch(cmHandleQueryApiParameters);
         final List<RestOutputCmHandle> outputCmHandles =
                 cmHandles.stream().map(this::toRestOutputCmHandle).collect(Collectors.toList());
@@ -253,7 +253,8 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
             final CmHandleQueryParameters cmHandleQueryParameters) {
         final CmHandleQueryApiParameters cmHandleQueryApiParameters =
                 jsonObjectMapper.convertToValueType(cmHandleQueryParameters, CmHandleQueryApiParameters.class);
-        final Set<String> cmHandleIds = networkCmProxyDataService.executeCmHandleIdSearch(cmHandleQueryApiParameters);
+        final Collection<String> cmHandleIds
+            = networkCmProxyDataService.executeCmHandleIdSearch(cmHandleQueryApiParameters);
         return ResponseEntity.ok(List.copyOf(cmHandleIds));
     }
 
index 0c27d3e..5d8599a 100755 (executable)
@@ -22,8 +22,8 @@
 package org.onap.cps.ncmp.rest.controller;
 
 import io.micrometer.core.annotation.Timed;
+import java.util.Collection;
 import java.util.List;
-import java.util.Set;
 import java.util.stream.Collectors;
 import javax.validation.Valid;
 import lombok.RequiredArgsConstructor;
@@ -55,7 +55,7 @@ public class NetworkCmProxyInventoryController implements NetworkCmProxyInventor
         final CmHandleQueryServiceParameters cmHandleQueryServiceParameters = ncmpRestInputMapper
                 .toCmHandleQueryServiceParameters(cmHandleQueryParameters);
 
-        final Set<String> cmHandleIds = networkCmProxyDataService
+        final Collection<String> cmHandleIds = networkCmProxyDataService
                 .executeCmHandleIdSearchForInventory(cmHandleQueryServiceParameters);
         return ResponseEntity.ok(List.copyOf(cmHandleIds));
     }
@@ -68,7 +68,7 @@ public class NetworkCmProxyInventoryController implements NetworkCmProxyInventor
      */
     @Override
     public ResponseEntity<List<String>> getAllCmHandleIdsForRegisteredDmi(final String dmiPluginIdentifier) {
-        final Set<String> cmHandleIds =
+        final Collection<String> cmHandleIds =
                 networkCmProxyDataService.getAllCmHandleIdsByDmiPluginIdentifier(dmiPluginIdentifier);
         return ResponseEntity.ok(List.copyOf(cmHandleIds));
     }
index e8c2d93..67bc313 100644 (file)
@@ -27,7 +27,7 @@
     <parent>
         <groupId>org.onap.cps</groupId>
         <artifactId>cps-parent</artifactId>
-        <version>3.2.3-SNAPSHOT</version>
+        <version>3.2.4-SNAPSHOT</version>
         <relativePath>../cps-parent/pom.xml</relativePath>
     </parent>
 
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022 Nordix Foundation
+ *  Copyright (C) 2022-2023 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.ncmp.api;
 
-import java.util.Set;
+import java.util.Collection;
 import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters;
 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
 
-public interface NetworkCmProxyCmHandlerQueryService {
+public interface NetworkCmProxyCmHandleQueryService {
     /**
      * Query and return cm handles that match the given query parameters.
      *
      * @param cmHandleQueryServiceParameters the cm handle query parameters
      * @return collection of cm handles
      */
-    Set<NcmpServiceCmHandle> queryCmHandles(CmHandleQueryServiceParameters cmHandleQueryServiceParameters);
+    Collection<NcmpServiceCmHandle> queryCmHandles(CmHandleQueryServiceParameters cmHandleQueryServiceParameters);
 
     /**
      * Query and return cm handles that match the given query parameters.
@@ -39,7 +39,7 @@ public interface NetworkCmProxyCmHandlerQueryService {
      * @param cmHandleQueryServiceParameters the cm handle query parameters
      * @return collection of cm handle ids
      */
-    Set<String> queryCmHandleIds(CmHandleQueryServiceParameters cmHandleQueryServiceParameters);
+    Collection<String> queryCmHandleIds(CmHandleQueryServiceParameters cmHandleQueryServiceParameters);
 
     /**
      * Query and return cm handles that match the given query parameters.
@@ -47,5 +47,5 @@ public interface NetworkCmProxyCmHandlerQueryService {
      * @param cmHandleQueryServiceParameters the cm handle query parameters
      * @return collection of cm handle ids
      */
-    Set<String> queryCmHandleIdsForInventory(CmHandleQueryServiceParameters cmHandleQueryServiceParameters);
+    Collection<String> queryCmHandleIdsForInventory(CmHandleQueryServiceParameters cmHandleQueryServiceParameters);
 }
index c9810e9..128eed3 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2021 highstreet technologies GmbH
- *  Modifications Copyright (C) 2021-2022 Nordix Foundation
+ *  Modifications Copyright (C) 2021-2023 Nordix Foundation
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2022 Bell Canada
  *  ================================================================================
@@ -27,7 +27,6 @@ import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum
 
 import java.util.Collection;
 import java.util.Map;
-import java.util.Set;
 import org.onap.cps.ncmp.api.inventory.CompositeState;
 import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters;
 import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters;
@@ -159,7 +158,7 @@ public interface NetworkCmProxyDataService {
      * @param cmHandleQueryApiParameters the cm handle query parameters
      * @return collection of cm handles
      */
-    Set<NcmpServiceCmHandle> executeCmHandleSearch(CmHandleQueryApiParameters cmHandleQueryApiParameters);
+    Collection<NcmpServiceCmHandle> executeCmHandleSearch(CmHandleQueryApiParameters cmHandleQueryApiParameters);
 
     /**
      * Query and return cm handle ids that match the given query parameters.
@@ -167,7 +166,7 @@ public interface NetworkCmProxyDataService {
      * @param cmHandleQueryApiParameters the cm handle query parameters
      * @return collection of cm handle ids
      */
-    Set<String> executeCmHandleIdSearch(CmHandleQueryApiParameters cmHandleQueryApiParameters);
+    Collection<String> executeCmHandleIdSearch(CmHandleQueryApiParameters cmHandleQueryApiParameters);
 
     /**
      * Set the data sync enabled flag, along with the data sync state to true or false based on the cm handle id.
@@ -181,15 +180,16 @@ public interface NetworkCmProxyDataService {
      * Get all cm handle IDs by DMI plugin identifier.
      *
      * @param dmiPluginIdentifier DMI plugin identifier
-     * @return set of cm handle IDs
+     * @return collection of cm handle IDs
      */
-    Set<String> getAllCmHandleIdsByDmiPluginIdentifier(String dmiPluginIdentifier);
+    Collection<String> getAllCmHandleIdsByDmiPluginIdentifier(String dmiPluginIdentifier);
 
     /**
      * Get all cm handle IDs by various search criteria.
      *
      * @param cmHandleQueryServiceParameters cm handle query parameters
-     * @return set of cm handle IDs
+     * @return collection of cm handle IDs
      */
-    Set<String> executeCmHandleIdSearchForInventory(CmHandleQueryServiceParameters cmHandleQueryServiceParameters);
+    Collection<String> executeCmHandleIdSearchForInventory(CmHandleQueryServiceParameters
+                                                               cmHandleQueryServiceParameters);
 }
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandleQueryServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandleQueryServiceImpl.java
new file mode 100644 (file)
index 0000000..54d89ba
--- /dev/null
@@ -0,0 +1,268 @@
+/*
+ *  ============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.ncmp.api.impl;
+
+import static org.onap.cps.ncmp.api.impl.utils.CmHandleQueryConditions.HAS_ALL_MODULES;
+import static org.onap.cps.ncmp.api.impl.utils.CmHandleQueryConditions.HAS_ALL_PROPERTIES;
+import static org.onap.cps.ncmp.api.impl.utils.CmHandleQueryConditions.WITH_CPS_PATH;
+import static org.onap.cps.ncmp.api.impl.utils.RestQueryParametersValidator.validateCpsPathConditionProperties;
+import static org.onap.cps.ncmp.api.impl.utils.RestQueryParametersValidator.validateModuleNameConditionProperties;
+import static org.onap.cps.ncmp.api.impl.utils.YangDataConverter.convertYangModelCmHandleToNcmpServiceCmHandle;
+import static org.onap.cps.spi.FetchDescendantsOption.DIRECT_CHILDREN_ONLY;
+import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.cpspath.parser.PathParsingException;
+import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService;
+import org.onap.cps.ncmp.api.impl.utils.InventoryQueryConditions;
+import org.onap.cps.ncmp.api.impl.utils.YangDataConverter;
+import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
+import org.onap.cps.ncmp.api.inventory.CmHandleQueries;
+import org.onap.cps.ncmp.api.inventory.InventoryPersistence;
+import org.onap.cps.ncmp.api.inventory.enums.PropertyType;
+import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters;
+import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
+import org.onap.cps.spi.exceptions.DataValidationException;
+import org.onap.cps.spi.model.ConditionProperties;
+import org.onap.cps.spi.model.DataNode;
+import org.springframework.stereotype.Service;
+
+@Service
+@Slf4j
+@RequiredArgsConstructor
+public class NetworkCmProxyCmHandleQueryServiceImpl implements NetworkCmProxyCmHandleQueryService {
+
+    private static final Collection<String> NO_QUERY_TO_EXECUTE = null;
+    private final CmHandleQueries cmHandleQueries;
+    private final InventoryPersistence inventoryPersistence;
+
+    @Override
+    public Collection<String> queryCmHandleIds(
+            final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) {
+        return executeQueries(cmHandleQueryServiceParameters,
+            this::executeCpsPathQuery,
+            this::queryCmHandlesByPublicProperties,
+            this::executeModuleNameQuery);
+    }
+
+    @Override
+    public Collection<String> queryCmHandleIdsForInventory(
+        final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) {
+        return executeQueries(cmHandleQueryServiceParameters,
+            this::queryCmHandlesByPublicProperties,
+            this::queryCmHandlesByPrivateProperties,
+            this::queryCmHandlesByDmiPlugin);
+    }
+
+    @Override
+    public Collection<NcmpServiceCmHandle> queryCmHandles(
+        final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) {
+
+        if (cmHandleQueryServiceParameters.getCmHandleQueryParameters().isEmpty()) {
+            return getAllCmHandles();
+        }
+
+        final Collection<String> cmHandleIds = queryCmHandleIds(cmHandleQueryServiceParameters);
+
+        return getNcmpServiceCmHandles(cmHandleIds);
+    }
+
+    private Collection<String> queryCmHandlesByDmiPlugin(
+            final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) {
+        final Map<String, String> dmiPropertyQueryPairs =
+                getPropertyPairs(cmHandleQueryServiceParameters.getCmHandleQueryParameters(),
+                        InventoryQueryConditions.CM_HANDLE_WITH_DMI_PLUGIN.getName());
+        if (dmiPropertyQueryPairs.isEmpty()) {
+            return NO_QUERY_TO_EXECUTE;
+        }
+
+        final String dmiPluginIdentifierValue = dmiPropertyQueryPairs
+            .get(PropertyType.DMI_PLUGIN.getYangContainerName());
+
+        return cmHandleQueries.getCmHandleIdsByDmiPluginIdentifier(dmiPluginIdentifierValue);
+    }
+
+    private Collection<String> queryCmHandlesByPrivateProperties(
+            final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) {
+
+        final Map<String, String> privatePropertyQueryPairs =
+                getPropertyPairs(cmHandleQueryServiceParameters.getCmHandleQueryParameters(),
+                        InventoryQueryConditions.HAS_ALL_ADDITIONAL_PROPERTIES.getName());
+
+        return privatePropertyQueryPairs.isEmpty()
+                ? NO_QUERY_TO_EXECUTE
+                : cmHandleQueries.queryCmHandleAdditionalProperties(privatePropertyQueryPairs);
+    }
+
+    private Collection<String> queryCmHandlesByPublicProperties(
+            final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) {
+
+        final Map<String, String> publicPropertyQueryPairs =
+                getPropertyPairs(cmHandleQueryServiceParameters.getCmHandleQueryParameters(),
+                        HAS_ALL_PROPERTIES.getConditionName());
+
+        return publicPropertyQueryPairs.isEmpty()
+                ? NO_QUERY_TO_EXECUTE
+                : cmHandleQueries.queryCmHandlePublicProperties(publicPropertyQueryPairs);
+    }
+
+    private Collection<String> executeModuleNameQuery(
+            final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) {
+        final Collection<String> moduleNamesForQuery =
+                getModuleNamesForQuery(cmHandleQueryServiceParameters.getCmHandleQueryParameters());
+        if (moduleNamesForQuery.isEmpty()) {
+            return NO_QUERY_TO_EXECUTE;
+        }
+        return inventoryPersistence.getCmHandleIdsWithGivenModules(moduleNamesForQuery);
+    }
+
+    private Collection<String> executeCpsPathQuery(
+            final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) {
+        final Map<String, String> cpsPathCondition
+            = getCpsPathCondition(cmHandleQueryServiceParameters.getCmHandleQueryParameters());
+        if (!validateCpsPathConditionProperties(cpsPathCondition)) {
+            return Collections.emptySet();
+        }
+        final Collection<String> cpsPathQueryResult;
+        if (cpsPathCondition.isEmpty()) {
+            return NO_QUERY_TO_EXECUTE;
+        }
+        try {
+            cpsPathQueryResult = collectCmHandleIdsFromDataNodes(
+                cmHandleQueries.queryCmHandleDataNodesByCpsPath(cpsPathCondition.get("cpsPath"), OMIT_DESCENDANTS));
+        } catch (final PathParsingException pathParsingException) {
+            throw new DataValidationException(pathParsingException.getMessage(), pathParsingException.getDetails(),
+                    pathParsingException);
+        }
+        return cpsPathQueryResult;
+    }
+
+    private Collection<String> getModuleNamesForQuery(final List<ConditionProperties> conditionProperties) {
+        final List<String> result = new ArrayList<>();
+        getConditions(conditionProperties, HAS_ALL_MODULES.getConditionName()).forEach(
+                conditionProperty -> {
+                    validateModuleNameConditionProperties(conditionProperty);
+                    result.add(conditionProperty.get("moduleName"));
+                });
+        return result;
+    }
+
+    private Map<String, String> getCpsPathCondition(final List<ConditionProperties> conditionProperties) {
+        final Map<String, String> result = new HashMap<>();
+        getConditions(conditionProperties, WITH_CPS_PATH.getConditionName()).forEach(result::putAll);
+        return result;
+    }
+
+    private Map<String, String> getPropertyPairs(final List<ConditionProperties> conditionProperties,
+                                                       final String queryProperty) {
+        final Map<String, String> result = new HashMap<>();
+        getConditions(conditionProperties, queryProperty).forEach(result::putAll);
+        return result;
+    }
+
+    private List<Map<String, String>> getConditions(final List<ConditionProperties> conditionProperties,
+                                                    final String name) {
+        for (final ConditionProperties conditionProperty : conditionProperties) {
+            if (conditionProperty.getConditionName().equals(name)) {
+                return conditionProperty.getConditionParameters();
+            }
+        }
+        return Collections.emptyList();
+    }
+
+    private Collection<NcmpServiceCmHandle> getAllCmHandles() {
+        final DataNode dataNode = inventoryPersistence.getDataNode("/dmi-registry").iterator().next();
+        return dataNode.getChildDataNodes().stream().map(this::createNcmpServiceCmHandle).collect(Collectors.toSet());
+    }
+
+    private Collection<String> getAllCmHandleIds() {
+        final DataNode dataNode = inventoryPersistence.getDataNode("/dmi-registry", DIRECT_CHILDREN_ONLY)
+                .iterator().next();
+        return collectCmHandleIdsFromDataNodes(dataNode.getChildDataNodes());
+    }
+
+    private Collection<NcmpServiceCmHandle> getNcmpServiceCmHandles(final Collection<String> cmHandleIds) {
+        final Collection<YangModelCmHandle> yangModelcmHandles
+            = inventoryPersistence.getYangModelCmHandles(cmHandleIds);
+
+        final Collection<NcmpServiceCmHandle> ncmpServiceCmHandles = new ArrayList<>(yangModelcmHandles.size());
+
+        yangModelcmHandles.forEach(yangModelcmHandle ->
+            ncmpServiceCmHandles.add(YangDataConverter.convertYangModelCmHandleToNcmpServiceCmHandle(yangModelcmHandle))
+        );
+        return ncmpServiceCmHandles;
+    }
+
+    private NcmpServiceCmHandle createNcmpServiceCmHandle(final DataNode dataNode) {
+        return convertYangModelCmHandleToNcmpServiceCmHandle(YangDataConverter
+                .convertCmHandleToYangModel(dataNode, dataNode.getLeaves().get("id").toString()));
+    }
+
+    private Collection<String> executeQueries(final CmHandleQueryServiceParameters cmHandleQueryServiceParameters,
+                                              final Function<CmHandleQueryServiceParameters, Collection<String>>...
+                                                  queryFunctions) {
+        if (cmHandleQueryServiceParameters.getCmHandleQueryParameters().isEmpty()) {
+            return getAllCmHandleIds();
+        }
+        Collection<String> combinedQueryResult = NO_QUERY_TO_EXECUTE;
+        for (final Function<CmHandleQueryServiceParameters, Collection<String>> queryFunction : queryFunctions) {
+            final Collection<String> queryResult = queryFunction.apply(cmHandleQueryServiceParameters);
+            if (noEntriesFoundCanStopQuerying(queryResult)) {
+                return Collections.emptySet();
+            }
+            combinedQueryResult = combineCmHandleQueryResults(combinedQueryResult, queryResult);
+        }
+        return combinedQueryResult;
+    }
+
+    private boolean noEntriesFoundCanStopQuerying(final Collection<String> queryResult) {
+        return queryResult != NO_QUERY_TO_EXECUTE && queryResult.isEmpty();
+    }
+
+    private Collection<String> combineCmHandleQueryResults(final Collection<String> firstQuery,
+                                                           final Collection<String> secondQuery) {
+        if (firstQuery == NO_QUERY_TO_EXECUTE && secondQuery == NO_QUERY_TO_EXECUTE) {
+            return NO_QUERY_TO_EXECUTE;
+        } else if (firstQuery == NO_QUERY_TO_EXECUTE) {
+            return secondQuery;
+        } else if (secondQuery == NO_QUERY_TO_EXECUTE) {
+            return firstQuery;
+        } else {
+            firstQuery.retainAll(secondQuery);
+            return firstQuery;
+        }
+    }
+
+    private Collection<String> collectCmHandleIdsFromDataNodes(final Collection<DataNode> dataNodes) {
+        return dataNodes.stream().map(dataNode -> (String) dataNode.getLeaves().get("id")).collect(Collectors.toSet());
+    }
+
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceImpl.java
deleted file mode 100644 (file)
index b67ae0c..0000000
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- *  ============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.ncmp.api.impl;
-
-import static org.onap.cps.ncmp.api.impl.utils.RestQueryParametersValidator.validateCpsPathConditionProperties;
-import static org.onap.cps.ncmp.api.impl.utils.RestQueryParametersValidator.validateModuleNameConditionProperties;
-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 java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.cpspath.parser.PathParsingException;
-import org.onap.cps.ncmp.api.NetworkCmProxyCmHandlerQueryService;
-import org.onap.cps.ncmp.api.impl.utils.CmHandleQueryConditions;
-import org.onap.cps.ncmp.api.impl.utils.InventoryQueryConditions;
-import org.onap.cps.ncmp.api.impl.utils.YangDataConverter;
-import org.onap.cps.ncmp.api.inventory.CmHandleQueries;
-import org.onap.cps.ncmp.api.inventory.InventoryPersistence;
-import org.onap.cps.ncmp.api.inventory.enums.PropertyType;
-import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters;
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
-import org.onap.cps.spi.exceptions.DataValidationException;
-import org.onap.cps.spi.model.ConditionProperties;
-import org.onap.cps.spi.model.DataNode;
-import org.springframework.stereotype.Service;
-
-@Service
-@Slf4j
-@RequiredArgsConstructor
-public class NetworkCmProxyCmHandlerQueryServiceImpl implements NetworkCmProxyCmHandlerQueryService {
-
-    private static final Map<String, NcmpServiceCmHandle> NO_QUERY_TO_EXECUTE = null;
-    private final CmHandleQueries cmHandleQueries;
-    private final InventoryPersistence inventoryPersistence;
-
-    /**
-     * Query and return cm handles that match the given query parameters.
-     *
-     * @param cmHandleQueryServiceParameters the cm handle query parameters
-     * @return collection of cm handles
-     */
-    @Override
-    public Set<NcmpServiceCmHandle> queryCmHandles(
-            final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) {
-
-        if (cmHandleQueryServiceParameters.getCmHandleQueryParameters().isEmpty()) {
-            return getAllCmHandles();
-        }
-
-        final Map<String, NcmpServiceCmHandle> combinedQueryResult = executeInventoryQueries(
-                cmHandleQueryServiceParameters);
-
-        return new HashSet<>(combineWithModuleNameQuery(cmHandleQueryServiceParameters, combinedQueryResult).values());
-    }
-
-    /**
-     * Query and return cm handles that match the given query parameters.
-     *
-     * @param cmHandleQueryServiceParameters the cm handle query parameters
-     * @return collection of cm handle ids
-     */
-    @Override
-    public Set<String> queryCmHandleIds(
-            final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) {
-
-        if (cmHandleQueryServiceParameters.getCmHandleQueryParameters().isEmpty()) {
-            return getAllCmHandleIds();
-        }
-
-        final Map<String, NcmpServiceCmHandle> combinedQueryResult = executeInventoryQueries(
-                cmHandleQueryServiceParameters);
-
-        final Collection<String> moduleNamesForQuery =
-                getModuleNamesForQuery(cmHandleQueryServiceParameters.getCmHandleQueryParameters());
-        if (moduleNamesForQuery.isEmpty()) {
-            return combinedQueryResult.keySet();
-        }
-        final Set<String> moduleNameQueryResult =
-                new HashSet<>(inventoryPersistence.getCmHandleIdsWithGivenModules(moduleNamesForQuery));
-
-        if (combinedQueryResult == NO_QUERY_TO_EXECUTE) {
-            return moduleNameQueryResult;
-        }
-
-        moduleNameQueryResult.retainAll(combinedQueryResult.keySet());
-        return moduleNameQueryResult;
-    }
-
-    @Override
-    public Set<String> queryCmHandleIdsForInventory(
-            final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) {
-
-        if (cmHandleQueryServiceParameters.getCmHandleQueryParameters().isEmpty()) {
-            return getAllCmHandleIds();
-        }
-
-        final Map<String, NcmpServiceCmHandle> publicPropertiesQueryResult = queryCmHandlesByPublicProperties(
-                cmHandleQueryServiceParameters);
-        if (publicPropertiesQueryResult != null && publicPropertiesQueryResult.isEmpty()) {
-            return Collections.emptySet();
-        }
-
-        final Map<String, NcmpServiceCmHandle> privatePropertiesQueryResult = queryCmHandlesByPrivateProperties(
-                cmHandleQueryServiceParameters);
-        if (privatePropertiesQueryResult != null && privatePropertiesQueryResult.isEmpty()) {
-            return Collections.emptySet();
-        }
-
-        final Map<String, NcmpServiceCmHandle> dmiPropertiesQueryResult = queryCmHandlesByDmiPlugin(
-                cmHandleQueryServiceParameters);
-        if (dmiPropertiesQueryResult != null && dmiPropertiesQueryResult.isEmpty()) {
-            return Collections.emptySet();
-        }
-
-        final Map<String, NcmpServiceCmHandle> combinedResult =
-              combineQueryResults(publicPropertiesQueryResult, privatePropertiesQueryResult, dmiPropertiesQueryResult);
-
-        return combinedResult.keySet();
-    }
-
-    private Map<String, NcmpServiceCmHandle> queryCmHandlesByDmiPlugin(
-            final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) {
-        final Map<String, String> dmiPropertyQueryPairs =
-                getPropertyPairs(cmHandleQueryServiceParameters.getCmHandleQueryParameters(),
-                        InventoryQueryConditions.CM_HANDLE_WITH_DMI_PLUGIN.getName());
-        if (dmiPropertyQueryPairs.isEmpty()) {
-            return NO_QUERY_TO_EXECUTE;
-        }
-
-        final String dmiPluginIdentifierValue = dmiPropertyQueryPairs.get(
-                PropertyType.DMI_PLUGIN.getYangContainerName());
-
-        final Set<NcmpServiceCmHandle> cmHandlesByDmiPluginIdentifier = cmHandleQueries
-                .getCmHandlesByDmiPluginIdentifier(dmiPluginIdentifierValue);
-
-        return cmHandlesByDmiPluginIdentifier.stream()
-                .collect(Collectors.toMap(NcmpServiceCmHandle::getCmHandleId, cmH -> cmH));
-    }
-
-    private Map<String, NcmpServiceCmHandle> queryCmHandlesByPrivateProperties(
-            final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) {
-
-        final Map<String, String> privatePropertyQueryPairs =
-                getPropertyPairs(cmHandleQueryServiceParameters.getCmHandleQueryParameters(),
-                        InventoryQueryConditions.HAS_ALL_ADDITIONAL_PROPERTIES.getName());
-
-        return privatePropertyQueryPairs.isEmpty()
-                ? NO_QUERY_TO_EXECUTE
-                : cmHandleQueries.queryCmHandleAdditionalProperties(privatePropertyQueryPairs);
-    }
-
-    private Map<String, NcmpServiceCmHandle> queryCmHandlesByPublicProperties(
-            final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) {
-
-        final Map<String, String> publicPropertyQueryPairs =
-                getPropertyPairs(cmHandleQueryServiceParameters.getCmHandleQueryParameters(),
-                        CmHandleQueryConditions.HAS_ALL_PROPERTIES.getConditionName());
-
-        return publicPropertyQueryPairs.isEmpty()
-                ? NO_QUERY_TO_EXECUTE
-                : cmHandleQueries.queryCmHandlePublicProperties(publicPropertyQueryPairs);
-    }
-
-    private Map<String, NcmpServiceCmHandle> combineQueryResults(
-            final Map<String, NcmpServiceCmHandle> publicPropertiesQueryResult,
-            final Map<String, NcmpServiceCmHandle> privatePropertiesQueryResult,
-            final Map<String, NcmpServiceCmHandle> dmiPropertiesQueryResult) {
-
-        final Map<String, NcmpServiceCmHandle> propertiesCombinedResult = cmHandleQueries
-                .combineCmHandleQueries(publicPropertiesQueryResult, privatePropertiesQueryResult);
-        return cmHandleQueries
-                .combineCmHandleQueries(propertiesCombinedResult, dmiPropertiesQueryResult);
-    }
-
-    private Map<String, NcmpServiceCmHandle> combineWithModuleNameQuery(
-            final CmHandleQueryServiceParameters cmHandleQueryServiceParameters,
-            final Map<String, NcmpServiceCmHandle> previousQueryResult) {
-        final Collection<String> moduleNamesForQuery =
-                getModuleNamesForQuery(cmHandleQueryServiceParameters.getCmHandleQueryParameters());
-        if (moduleNamesForQuery.isEmpty()) {
-            return previousQueryResult;
-        }
-        final Collection<String> cmHandleIdsByModuleName =
-                inventoryPersistence.getCmHandleIdsWithGivenModules(moduleNamesForQuery);
-        if (cmHandleIdsByModuleName.isEmpty()) {
-            return Collections.emptyMap();
-        }
-        final Map<String, NcmpServiceCmHandle> queryResult = new HashMap<>(cmHandleIdsByModuleName.size());
-        if (previousQueryResult == NO_QUERY_TO_EXECUTE) {
-            cmHandleIdsByModuleName.forEach(cmHandleId ->
-                    queryResult.put(cmHandleId, createNcmpServiceCmHandle(
-                            inventoryPersistence.getDataNode("/dmi-registry/cm-handles[@id='" + cmHandleId + "']")))
-            );
-            return queryResult;
-        }
-        previousQueryResult.keySet().retainAll(cmHandleIdsByModuleName);
-        queryResult.putAll(previousQueryResult);
-        return queryResult;
-    }
-
-    private Map<String, NcmpServiceCmHandle> executeInventoryQueries(
-            final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) {
-        final Map<String, String> cpsPath = getCpsPath(cmHandleQueryServiceParameters.getCmHandleQueryParameters());
-        if (!validateCpsPathConditionProperties(cpsPath)) {
-            return Collections.emptyMap();
-        }
-        final Map<String, NcmpServiceCmHandle> cpsPathQueryResult;
-        if (cpsPath.isEmpty()) {
-            cpsPathQueryResult = NO_QUERY_TO_EXECUTE;
-        } else {
-            try {
-                cpsPathQueryResult = cmHandleQueries.queryCmHandleDataNodesByCpsPath(
-                                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);
-            }
-            if (cpsPathQueryResult.isEmpty()) {
-                return Collections.emptyMap();
-            }
-        }
-
-        final Map<String, String> publicPropertyQueryPairs =
-                getPropertyPairs(cmHandleQueryServiceParameters.getCmHandleQueryParameters(),
-                        CmHandleQueryConditions.HAS_ALL_PROPERTIES.getConditionName());
-        final Map<String, NcmpServiceCmHandle> propertiesQueryResult = publicPropertyQueryPairs.isEmpty()
-                ? NO_QUERY_TO_EXECUTE : cmHandleQueries.queryCmHandlePublicProperties(publicPropertyQueryPairs);
-
-        return cmHandleQueries.combineCmHandleQueries(cpsPathQueryResult, propertiesQueryResult);
-    }
-
-    private Collection<String> getModuleNamesForQuery(final List<ConditionProperties> conditionProperties) {
-        final List<String> result = new ArrayList<>();
-        getConditions(conditionProperties, CmHandleQueryConditions.HAS_ALL_MODULES.getConditionName())
-            .parallelStream().forEach(
-                conditionProperty -> {
-                    validateModuleNameConditionProperties(conditionProperty);
-                    result.add(conditionProperty.get("moduleName"));
-                }
-            );
-        return result;
-    }
-
-    private Map<String, String> getCpsPath(final List<ConditionProperties> conditionProperties) {
-        final Map<String, String> result = new HashMap<>();
-        getConditions(conditionProperties, CmHandleQueryConditions.WITH_CPS_PATH.getConditionName()).forEach(
-                result::putAll);
-        return result;
-    }
-
-    private Map<String, String> getPropertyPairs(final List<ConditionProperties> conditionProperties,
-                                                       final String queryProperty) {
-        final Map<String, String> result = new HashMap<>();
-        getConditions(conditionProperties, queryProperty).forEach(result::putAll);
-        return result;
-    }
-
-    private List<Map<String, String>> getConditions(final List<ConditionProperties> conditionProperties,
-                                                    final String name) {
-        for (final ConditionProperties conditionProperty : conditionProperties) {
-            if (conditionProperty.getConditionName().equals(name)) {
-                return conditionProperty.getConditionParameters();
-            }
-        }
-        return Collections.emptyList();
-    }
-
-    private Set<NcmpServiceCmHandle> getAllCmHandles() {
-        return inventoryPersistence.getDataNode("/dmi-registry")
-                .getChildDataNodes().stream().map(this::createNcmpServiceCmHandle).collect(Collectors.toSet());
-    }
-
-    private Set<String> getAllCmHandleIds() {
-        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()));
-    }
-}
index 508acdc..d3a4f53 100755 (executable)
@@ -4,6 +4,7 @@
  *  Modifications Copyright (C) 2021-2023 Nordix Foundation
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2021-2022 Bell Canada
+ *  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.
@@ -41,7 +42,7 @@ import java.util.stream.Collectors;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.api.CpsDataService;
-import org.onap.cps.ncmp.api.NetworkCmProxyCmHandlerQueryService;
+import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService;
 import org.onap.cps.ncmp.api.NetworkCmProxyDataService;
 import org.onap.cps.ncmp.api.impl.event.lcm.LcmEventsCmHandleStateHandler;
 import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations;
@@ -85,7 +86,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
     private final NetworkCmProxyDataServicePropertyHandler networkCmProxyDataServicePropertyHandler;
     private final InventoryPersistence inventoryPersistence;
     private final CmHandleQueries cmHandleQueries;
-    private final NetworkCmProxyCmHandlerQueryService networkCmProxyCmHandlerQueryService;
+    private final NetworkCmProxyCmHandleQueryService networkCmProxyCmHandleQueryService;
     private final LcmEventsCmHandleStateHandler lcmEventsCmHandleStateHandler;
     private final CpsDataService cpsDataService;
     private final IMap<String, Object> moduleSyncStartedOnCmHandles;
@@ -131,8 +132,8 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
     public Object getResourceDataOperational(final String cmHandleId,
                                              final String resourceIdentifier,
                                              final FetchDescendantsOption fetchDescendantsOption) {
-        return cpsDataService.getDataNode(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId, resourceIdentifier,
-                fetchDescendantsOption);
+        return cpsDataService.getDataNodes(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId, resourceIdentifier,
+                fetchDescendantsOption).iterator().next();
     }
 
     @Override
@@ -176,11 +177,12 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
      * @return cm handles with details
      */
     @Override
-    public Set<NcmpServiceCmHandle> executeCmHandleSearch(final CmHandleQueryApiParameters cmHandleQueryApiParameters) {
+    public Collection<NcmpServiceCmHandle> executeCmHandleSearch(
+            final CmHandleQueryApiParameters cmHandleQueryApiParameters) {
         final CmHandleQueryServiceParameters cmHandleQueryServiceParameters = jsonObjectMapper.convertToValueType(
                 cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class);
         validateCmHandleQueryParameters(cmHandleQueryServiceParameters, CmHandleQueryConditions.ALL_CONDITION_NAMES);
-        return networkCmProxyCmHandlerQueryService.queryCmHandles(cmHandleQueryServiceParameters);
+        return networkCmProxyCmHandleQueryService.queryCmHandles(cmHandleQueryServiceParameters);
     }
 
     /**
@@ -190,11 +192,11 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
      * @return cm handle ids
      */
     @Override
-    public Set<String> executeCmHandleIdSearch(final CmHandleQueryApiParameters cmHandleQueryApiParameters) {
+    public Collection<String> executeCmHandleIdSearch(final CmHandleQueryApiParameters cmHandleQueryApiParameters) {
         final CmHandleQueryServiceParameters cmHandleQueryServiceParameters = jsonObjectMapper.convertToValueType(
                 cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class);
         validateCmHandleQueryParameters(cmHandleQueryServiceParameters, CmHandleQueryConditions.ALL_CONDITION_NAMES);
-        return networkCmProxyCmHandlerQueryService.queryCmHandleIds(cmHandleQueryServiceParameters);
+        return networkCmProxyCmHandleQueryService.queryCmHandleIds(cmHandleQueryServiceParameters);
     }
 
     /**
@@ -233,12 +235,8 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
      * @return set of cm handle IDs
      */
     @Override
-    public Set<String> getAllCmHandleIdsByDmiPluginIdentifier(final String dmiPluginIdentifier) {
-        final Set<NcmpServiceCmHandle> ncmpServiceCmHandles =
-                cmHandleQueries.getCmHandlesByDmiPluginIdentifier(dmiPluginIdentifier);
-        final Set<String> cmHandleIds = new HashSet<>(ncmpServiceCmHandles.size());
-        ncmpServiceCmHandles.forEach(cmHandle -> cmHandleIds.add(cmHandle.getCmHandleId()));
-        return cmHandleIds;
+    public Collection<String> getAllCmHandleIdsByDmiPluginIdentifier(final String dmiPluginIdentifier) {
+        return cmHandleQueries.getCmHandleIdsByDmiPluginIdentifier(dmiPluginIdentifier);
     }
 
     /**
@@ -248,10 +246,10 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
      * @return set of cm handle IDs
      */
     @Override
-    public Set<String> executeCmHandleIdSearchForInventory(
+    public Collection<String> executeCmHandleIdSearchForInventory(
             final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) {
         validateCmHandleQueryParameters(cmHandleQueryServiceParameters, InventoryQueryConditions.ALL_CONDITION_NAMES);
-        return networkCmProxyCmHandlerQueryService.queryCmHandleIdsForInventory(cmHandleQueryServiceParameters);
+        return networkCmProxyCmHandleQueryService.queryCmHandleIdsForInventory(cmHandleQueryServiceParameters);
     }
 
     /**
@@ -425,4 +423,5 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
             return CmHandleRegistrationResponse.createFailureResponses(cmHandleIds, exception);
         }
     }
+
 }
index f39c2f9..bbb2c0f 100644 (file)
@@ -2,6 +2,7 @@
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2022 Nordix Foundation
  *  Modifications Copyright (C) 2022 Bell Canada
+ *  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.
@@ -66,7 +67,8 @@ public class NetworkCmProxyDataServicePropertyHandler {
         for (final NcmpServiceCmHandle ncmpServiceCmHandle : ncmpServiceCmHandles) {
             final String cmHandleId = ncmpServiceCmHandle.getCmHandleId();
             try {
-                final DataNode existingCmHandleDataNode = inventoryPersistence.getCmHandleDataNode(cmHandleId);
+                final DataNode existingCmHandleDataNode = inventoryPersistence.getCmHandleDataNode(cmHandleId)
+                        .iterator().next();
                 processUpdates(existingCmHandleDataNode, ncmpServiceCmHandle);
                 cmHandleRegistrationResponses.add(CmHandleRegistrationResponse.createSuccessResponse(cmHandleId));
             } catch (final DataNodeNotFoundException e) {
index bae0262..ff78f00 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022 Nordix Foundation
+ *  Copyright (C) 2022-2023 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.ncmp.api.inventory;
 
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
 import org.onap.cps.spi.FetchDescendantsOption;
 import org.onap.cps.spi.model.DataNode;
 
@@ -33,10 +32,9 @@ public interface CmHandleQueries {
      * Query CmHandles based on additional (private) properties.
      *
      * @param additionalPropertyQueryPairs private properties for query
-     * @return CmHandles which have these private properties
+     * @return Ids of CmHandles which have these private properties
      */
-    Map<String, NcmpServiceCmHandle> queryCmHandleAdditionalProperties(
-            Map<String, String> additionalPropertyQueryPairs);
+    Collection<String> queryCmHandleAdditionalProperties(Map<String, String> additionalPropertyQueryPairs);
 
     /**
      * Query CmHandles based on public properties.
@@ -44,18 +42,7 @@ public interface CmHandleQueries {
      * @param publicPropertyQueryPairs public properties for query
      * @return CmHandles which have these public properties
      */
-    Map<String, NcmpServiceCmHandle> queryCmHandlePublicProperties(
-            Map<String, String> publicPropertyQueryPairs);
-
-    /**
-     * Combine Maps of CmHandles.
-     *
-     * @param firstQuery  first CmHandles Map
-     * @param secondQuery second CmHandles Map
-     * @return combined Map of CmHandles
-     */
-    Map<String, NcmpServiceCmHandle> combineCmHandleQueries(Map<String, NcmpServiceCmHandle> firstQuery,
-            Map<String, NcmpServiceCmHandle> secondQuery);
+    Collection<String> queryCmHandlePublicProperties(Map<String, String> publicPropertyQueryPairs);
 
     /**
      * Method which returns cm handles by the cm handles state.
@@ -91,10 +78,10 @@ public interface CmHandleQueries {
     List<DataNode> queryCmHandlesByOperationalSyncState(DataStoreSyncState dataStoreSyncState);
 
     /**
-     * Get all cm handles by DMI plugin identifier.
+     * Get all cm handles ids by DMI plugin identifier.
      *
      * @param dmiPluginIdentifier DMI plugin identifier
-     * @return set of cm handles
+     * @return collection of cm handles
      */
-    Set<NcmpServiceCmHandle> getCmHandlesByDmiPluginIdentifier(String dmiPluginIdentifier);
+    Collection<String> getCmHandleIdsByDmiPluginIdentifier(String dmiPluginIdentifier);
 }
index bda0a72..f61d6c3 100644 (file)
@@ -1,6 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022 Nordix Foundation
+ *  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.
 
 package org.onap.cps.ncmp.api.inventory;
 
-import static org.onap.cps.ncmp.api.impl.utils.YangDataConverter.convertYangModelCmHandleToNcmpServiceCmHandle;
 import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS;
 import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS;
 
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.stream.Collectors;
 import lombok.RequiredArgsConstructor;
-import org.onap.cps.ncmp.api.impl.utils.YangDataConverter;
 import org.onap.cps.ncmp.api.inventory.enums.PropertyType;
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
 import org.onap.cps.spi.CpsDataPersistenceService;
 import org.onap.cps.spi.FetchDescendantsOption;
 import org.onap.cps.spi.model.DataNode;
@@ -50,63 +46,18 @@ public class CmHandleQueriesImpl implements CmHandleQueries {
     private static final String DESCENDANT_PATH = "//";
 
     private final CpsDataPersistenceService cpsDataPersistenceService;
-    private static final Map<String, NcmpServiceCmHandle> NO_QUERY_TO_EXECUTE = null;
     private static final String ANCESTOR_CM_HANDLES = "/ancestor::cm-handles";
 
     @Override
-    public Map<String, NcmpServiceCmHandle> queryCmHandleAdditionalProperties(
-            final Map<String, String> privatePropertyQueryPairs) {
+    public Collection<String> queryCmHandleAdditionalProperties(final Map<String, String> privatePropertyQueryPairs) {
         return queryCmHandleAnyProperties(privatePropertyQueryPairs, PropertyType.ADDITIONAL);
     }
 
     @Override
-    public Map<String, NcmpServiceCmHandle> queryCmHandlePublicProperties(
-            final Map<String, String> publicPropertyQueryPairs) {
+    public Collection<String> queryCmHandlePublicProperties(final Map<String, String> publicPropertyQueryPairs) {
         return queryCmHandleAnyProperties(publicPropertyQueryPairs, PropertyType.PUBLIC);
     }
 
-    private  Map<String, NcmpServiceCmHandle> queryCmHandleAnyProperties(
-            final Map<String, String> propertyQueryPairs,
-            final PropertyType propertyType) {
-        if (propertyQueryPairs.isEmpty()) {
-            return Collections.emptyMap();
-        }
-        Map<String, NcmpServiceCmHandle> cmHandleIdToNcmpServiceCmHandles = null;
-        for (final Map.Entry<String, String> publicPropertyQueryPair : propertyQueryPairs.entrySet()) {
-            final String cpsPath = DESCENDANT_PATH + propertyType.getYangContainerName() + "[@name=\""
-                    + publicPropertyQueryPair.getKey()
-                    + "\" and @value=\"" + publicPropertyQueryPair.getValue() + "\"]";
-
-            final Collection<DataNode> dataNodes = queryCmHandleDataNodesByCpsPath(cpsPath, INCLUDE_ALL_DESCENDANTS);
-            if (cmHandleIdToNcmpServiceCmHandles == null) {
-                cmHandleIdToNcmpServiceCmHandles = collectDataNodesToNcmpServiceCmHandles(dataNodes);
-            } else {
-                final Collection<String> cmHandleIdsToRetain = dataNodes.parallelStream()
-                        .map(dataNode -> dataNode.getLeaves().get("id").toString()).collect(Collectors.toSet());
-                cmHandleIdToNcmpServiceCmHandles.keySet().retainAll(cmHandleIdsToRetain);
-            }
-            if (cmHandleIdToNcmpServiceCmHandles.isEmpty()) {
-                break;
-            }
-        }
-        return cmHandleIdToNcmpServiceCmHandles;
-    }
-
-    @Override
-    public Map<String, NcmpServiceCmHandle> combineCmHandleQueries(final Map<String, NcmpServiceCmHandle> firstQuery,
-            final Map<String, NcmpServiceCmHandle> secondQuery) {
-        if (firstQuery == NO_QUERY_TO_EXECUTE && secondQuery == NO_QUERY_TO_EXECUTE) {
-            return NO_QUERY_TO_EXECUTE;
-        } else if (firstQuery == NO_QUERY_TO_EXECUTE) {
-            return secondQuery;
-        } else if (secondQuery == NO_QUERY_TO_EXECUTE) {
-            return firstQuery;
-        } else {
-            firstQuery.keySet().retainAll(secondQuery.keySet());
-            return firstQuery;
-        }
-    }
-
     @Override
     public List<DataNode> queryCmHandlesByState(final CmHandleState cmHandleState) {
         return queryCmHandleDataNodesByCpsPath("//state[@cm-handle-state=\"" + cmHandleState + "\"]",
@@ -133,36 +84,47 @@ public class CmHandleQueriesImpl implements CmHandleQueries {
                 + dataStoreSyncState + "\"]", FetchDescendantsOption.OMIT_DESCENDANTS);
     }
 
-    private Map<String, NcmpServiceCmHandle> collectDataNodesToNcmpServiceCmHandles(
-            final Collection<DataNode> dataNodes) {
-        final Map<String, NcmpServiceCmHandle> cmHandleIdToNcmpServiceCmHandle = new HashMap<>();
-        dataNodes.forEach(dataNode -> {
-            final NcmpServiceCmHandle ncmpServiceCmHandle = createNcmpServiceCmHandle(dataNode);
-            cmHandleIdToNcmpServiceCmHandle.put(ncmpServiceCmHandle.getCmHandleId(), ncmpServiceCmHandle);
-        });
-        return cmHandleIdToNcmpServiceCmHandle;
-    }
-
-    private NcmpServiceCmHandle createNcmpServiceCmHandle(final DataNode dataNode) {
-        return convertYangModelCmHandleToNcmpServiceCmHandle(YangDataConverter
-                .convertCmHandleToYangModel(dataNode, dataNode.getLeaves().get("id").toString()));
-    }
-
     @Override
-    public Set<NcmpServiceCmHandle> getCmHandlesByDmiPluginIdentifier(final String dmiPluginIdentifier) {
-        final Map<String, DataNode> cmHandleAsDataNodePerCmHandleId = new HashMap<>();
+    public Collection<String> getCmHandleIdsByDmiPluginIdentifier(final String dmiPluginIdentifier) {
+        final Collection<String> cmHandleIds = new HashSet<>();
         for (final ModelledDmiServiceLeaves modelledDmiServiceLeaf : ModelledDmiServiceLeaves.values()) {
             for (final DataNode cmHandleAsDataNode: getCmHandlesByDmiPluginIdentifierAndDmiProperty(
                     dmiPluginIdentifier,
                     modelledDmiServiceLeaf.getLeafName())) {
-                cmHandleAsDataNodePerCmHandleId.put(
-                        cmHandleAsDataNode.getLeaves().get("id").toString(), cmHandleAsDataNode);
+                cmHandleIds.add(cmHandleAsDataNode.getLeaves().get("id").toString());
+            }
+        }
+        return cmHandleIds;
+    }
+
+    private Collection<String> collectCmHandleIdsFromDataNodes(final Collection<DataNode> dataNodes) {
+        return dataNodes.stream().map(dataNode -> (String) dataNode.getLeaves().get("id")).collect(Collectors.toSet());
+    }
+
+    private Collection<String> queryCmHandleAnyProperties(
+        final Map<String, String> propertyQueryPairs,
+        final PropertyType propertyType) {
+        if (propertyQueryPairs.isEmpty()) {
+            return Collections.emptySet();
+        }
+        Collection<String> cmHandleIds = null;
+        for (final Map.Entry<String, String> publicPropertyQueryPair : propertyQueryPairs.entrySet()) {
+            final String cpsPath = DESCENDANT_PATH + propertyType.getYangContainerName() + "[@name=\""
+                + publicPropertyQueryPair.getKey()
+                + "\" and @value=\"" + publicPropertyQueryPair.getValue() + "\"]";
+
+            final Collection<DataNode> dataNodes = queryCmHandleDataNodesByCpsPath(cpsPath, OMIT_DESCENDANTS);
+            if (cmHandleIds == null) {
+                cmHandleIds = collectCmHandleIdsFromDataNodes(dataNodes);
+            } else {
+                final Collection<String> cmHandleIdsToRetain = collectCmHandleIdsFromDataNodes(dataNodes);
+                cmHandleIds.retainAll(cmHandleIdsToRetain);
+            }
+            if (cmHandleIds.isEmpty()) {
+                break;
             }
         }
-        final Set<NcmpServiceCmHandle> ncmpServiceCmHandles = new HashSet<>(cmHandleAsDataNodePerCmHandleId.size());
-        cmHandleAsDataNodePerCmHandleId.values().forEach(
-                cmHandleAsDataNode -> ncmpServiceCmHandles.add(createNcmpServiceCmHandle(cmHandleAsDataNode)));
-        return ncmpServiceCmHandles;
+        return cmHandleIds;
     }
 
     private List<DataNode> getCmHandlesByDmiPluginIdentifierAndDmiProperty(final String dmiPluginIdentifier,
@@ -174,8 +136,8 @@ public class CmHandleQueriesImpl implements CmHandleQueries {
 
     private DataNode getCmHandleState(final String cmHandleId) {
         final String xpath = "/dmi-registry/cm-handles[@id='" + cmHandleId + "']/state";
-        return cpsDataPersistenceService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-                xpath, OMIT_DESCENDANTS);
+        return cpsDataPersistenceService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+                xpath, OMIT_DESCENDANTS).iterator().next();
     }
 }
 
index 73acf43..cbd30a8 100644 (file)
@@ -1,6 +1,7 @@
 /*
  *  ============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.
@@ -126,7 +127,7 @@ public interface InventoryPersistence {
      * @param xpath xpath
      * @return data node
      */
-    DataNode getDataNode(String xpath);
+    Collection<DataNode> getDataNode(String xpath);
 
     /**
      * Get data node via xpath.
@@ -135,7 +136,7 @@ public interface InventoryPersistence {
      * @param fetchDescendantsOption fetch descendants option
      * @return data node
      */
-    DataNode getDataNode(String xpath, FetchDescendantsOption fetchDescendantsOption);
+    Collection<DataNode> getDataNode(String xpath, FetchDescendantsOption fetchDescendantsOption);
 
     /**
      * Get collection of data nodes via xpaths.
@@ -160,7 +161,7 @@ public interface InventoryPersistence {
      * @param cmHandleId cmHandle ID
      * @return data node
      */
-    DataNode getCmHandleDataNode(String cmHandleId);
+    Collection<DataNode> getCmHandleDataNode(String cmHandleId);
 
     /**
      * Get collection of data nodes of given cm handles.
index 2c97895..c8d6c05 100644 (file)
@@ -2,6 +2,7 @@
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2022-2023 Nordix Foundation
  *  Modifications Copyright (C) 2022 Bell Canada
+ *  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.
@@ -73,9 +74,9 @@ public class InventoryPersistenceImpl implements InventoryPersistence {
 
     @Override
     public CompositeState getCmHandleState(final String cmHandleId) {
-        final DataNode stateAsDataNode = cpsDataService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+        final DataNode stateAsDataNode = cpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
                 createCmHandleXPath(cmHandleId) + "/state",
-                FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS);
+                FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS).iterator().next();
         cpsValidator.validateNameCharacters(cmHandleId);
         return new CompositeStateBuilder().fromDataNode(stateAsDataNode).build();
     }
@@ -101,7 +102,8 @@ public class InventoryPersistenceImpl implements InventoryPersistence {
     @Override
     public YangModelCmHandle getYangModelCmHandle(final String cmHandleId) {
         cpsValidator.validateNameCharacters(cmHandleId);
-        return YangDataConverter.convertCmHandleToYangModel(getCmHandleDataNode(cmHandleId), cmHandleId);
+        final DataNode dataNode = getCmHandleDataNode(cmHandleId).iterator().next();
+        return YangDataConverter.convertCmHandleToYangModel(dataNode, cmHandleId);
     }
 
     @Override
@@ -177,15 +179,15 @@ public class InventoryPersistenceImpl implements InventoryPersistence {
     @Override
     @Timed(value = "cps.ncmp.inventory.persistence.datanode.get",
             description = "Time taken to get a data node (from ncmp dmi registry)")
-    public DataNode getDataNode(final String xpath) {
+    public Collection<DataNode> getDataNode(final String xpath) {
         return getDataNode(xpath, INCLUDE_ALL_DESCENDANTS);
     }
 
     @Override
     @Timed(value = "cps.ncmp.inventory.persistence.datanode.get",
             description = "Time taken to get a data node (from ncmp dmi registry)")
-    public DataNode getDataNode(final String xpath, final FetchDescendantsOption fetchDescendantsOption) {
-        return cpsDataService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+    public Collection<DataNode> getDataNode(final String xpath, final FetchDescendantsOption fetchDescendantsOption) {
+        return cpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
                 xpath, fetchDescendantsOption);
     }
 
@@ -197,12 +199,12 @@ public class InventoryPersistenceImpl implements InventoryPersistence {
     @Override
     public Collection<DataNode> getDataNodes(final Collection<String> xpaths,
                                              final FetchDescendantsOption fetchDescendantsOption) {
-        return cpsDataService.getDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+        return cpsDataService.getDataNodesForMultipleXpaths(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
                 xpaths, fetchDescendantsOption);
     }
 
     @Override
-    public DataNode getCmHandleDataNode(final String cmHandleId) {
+    public Collection<DataNode> getCmHandleDataNode(final String cmHandleId) {
         return this.getDataNode(createCmHandleXPath(cmHandleId));
     }
 
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandleQueryServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandleQueryServiceSpec.groovy
new file mode 100644 (file)
index 0000000..bff8222
--- /dev/null
@@ -0,0 +1,211 @@
+/*
+ *  ============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.ncmp.api.impl
+
+import org.onap.cps.cpspath.parser.PathParsingException
+import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
+import org.onap.cps.ncmp.api.inventory.CmHandleQueries
+import org.onap.cps.ncmp.api.inventory.CmHandleQueriesImpl
+import org.onap.cps.ncmp.api.inventory.InventoryPersistence
+import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters
+import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
+import org.onap.cps.spi.FetchDescendantsOption
+import org.onap.cps.spi.exceptions.DataInUseException
+import org.onap.cps.spi.exceptions.DataValidationException
+import org.onap.cps.spi.model.ConditionProperties
+import org.onap.cps.spi.model.DataNode
+import spock.lang.Specification
+
+class NetworkCmProxyCmHandleQueryServiceSpec extends Specification {
+
+    def cmHandleQueries = Mock(CmHandleQueries)
+    def partiallyMockedCmHandleQueries = Spy(CmHandleQueriesImpl)
+    def mockInventoryPersistence = Mock(InventoryPersistence)
+
+    def dmiRegistry = new DataNode(xpath: '/dmi-registry', childDataNodes: createDataNodeList(['PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4']))
+
+    def objectUnderTest = new NetworkCmProxyCmHandleQueryServiceImpl(cmHandleQueries, mockInventoryPersistence)
+    def objectUnderTestWithPartiallyMockedQueries = new NetworkCmProxyCmHandleQueryServiceImpl(partiallyMockedCmHandleQueries, mockInventoryPersistence)
+
+    def 'Query cm handle ids with cpsPath.'() {
+        given: 'a cmHandleWithCpsPath condition property'
+            def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
+            def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/some/cps/path']])
+            cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties])
+        and: 'the query get the cm handle datanodes excluding all descendants returns a datanode'
+            cmHandleQueries.queryCmHandleDataNodesByCpsPath('/some/cps/path', FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(leaves: ['id':'some-cmhandle-id'])]
+        when: 'the query is executed for cm handle ids'
+            def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters)
+        then: 'the correct expected cm handles ids are returned'
+            assert result == ['some-cmhandle-id'] as Set
+    }
+
+    def 'Cm handle ids query with error: #scenario.'() {
+        given: 'a cmHandleWithCpsPath condition property'
+            def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
+            def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/some/cps/path']])
+            cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties])
+        and: 'cmHandleQueries throws a path parsing exception'
+            cmHandleQueries.queryCmHandleDataNodesByCpsPath('/some/cps/path', FetchDescendantsOption.OMIT_DESCENDANTS) >> { throw thrownException }
+        when: 'the query is executed for cm handle ids'
+            objectUnderTest.queryCmHandleIds(cmHandleQueryParameters)
+        then: 'a data validation exception is thrown'
+            thrown(expectedException)
+        where: 'the following data is used'
+            scenario               | thrownException                                          || expectedException
+            'PathParsingException' | new PathParsingException('some message', 'some details') || DataValidationException
+            'any other Exception'  | new DataInUseException('some message', 'some details')   || DataInUseException
+    }
+
+    def 'Cm handle ids cpsPath query for private properties (not allowed).'() {
+        given: 'a CpsPath condition property for private properties'
+            def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
+            def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/additional-properties']])
+            cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties])
+        when: 'the query is executed for cm handle ids'
+            def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters)
+        then: 'empty result is returned'
+            assert result.isEmpty()
+    }
+
+    def 'Query cm handle ids with module names when #scenario from query.'() {
+        given: 'a modules condition property'
+            def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
+            def conditionProperties = createConditionProperties('hasAllModules', [['moduleName': 'some-module-name']])
+            cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties])
+        when: 'the query is executed for cm handle ids'
+            def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters)
+        then: 'the inventory service is called with the correct module names'
+            1 * mockInventoryPersistence.getCmHandleIdsWithGivenModules(['some-module-name']) >> cmHandleIdsFromService
+        and: 'the correct expected cm handles ids are returned'
+            assert result.size() == cmHandleIdsFromService.size()
+            assert result.containsAll(cmHandleIdsFromService)
+        where: 'the following data is used'
+            scenario                  | cmHandleIdsFromService
+            'One anchor returned'     | ['some-cmhandle-id']
+            'No anchors are returned' | []
+    }
+
+    def 'Query cm handle details with module names when #scenario from query.'() {
+        given: 'a modules condition property'
+            def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
+            def conditionProperties = createConditionProperties('hasAllModules', [['moduleName': 'some-module-name']])
+            cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties])
+        when: 'the query is executed for cm handle ids'
+            def result = objectUnderTest.queryCmHandles(cmHandleQueryParameters)
+        then: 'the inventory service is called with the correct module names'
+            1 * mockInventoryPersistence.getCmHandleIdsWithGivenModules(['some-module-name']) >> ['ch1']
+        and: 'the inventory service is called with teh correct if and returns a yang model cm handle'
+            1 * mockInventoryPersistence.getYangModelCmHandles(['ch1']) >>
+                [new YangModelCmHandle(id: 'abc', dmiProperties: [new YangModelCmHandle.Property('name','value')], publicProperties: [])]
+        and: 'the expected cm handle(s) are returned as NCMP Service cm handles'
+            assert result[0] instanceof NcmpServiceCmHandle
+            assert result.size() == 1
+            assert result[0].dmiProperties == [name:'value']
+    }
+
+    def 'Query cm handle ids when the query is empty.'() {
+        given: 'We use an empty query'
+            def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
+        and: 'the inventory persistence returns the dmi registry datanode with just ids'
+            mockInventoryPersistence.getDataNode("/dmi-registry", FetchDescendantsOption.DIRECT_CHILDREN_ONLY) >> [dmiRegistry]
+        when: 'the query is executed for both cm handle ids'
+            def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters)
+        then: 'the correct expected cm handles are returned'
+            assert result.containsAll('PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4')
+    }
+
+    def 'Query cm handle details when the query is empty.'() {
+        given: 'We use an empty query'
+            def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
+        and: 'the inventory persistence returns the dmi registry datanode with just ids'
+            mockInventoryPersistence.getDataNode("/dmi-registry") >> [dmiRegistry]
+        when: 'the query is executed for both cm handle details'
+            def result = objectUnderTest.queryCmHandles(cmHandleQueryParameters)
+        then: 'the correct cm handles are returned'
+            assert result.size() == 4
+            assert result.cmHandleId.containsAll('PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4')
+    }
+
+    def 'Query CMHandleId with #scenario.' () {
+        given: 'a query object created with #condition'
+            def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
+            def conditionProperties = createConditionProperties(conditionName, [['some-key': 'some-value']])
+            cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties])
+        and: 'the inventoryPersistence returns different CmHandleIds'
+            partiallyMockedCmHandleQueries.queryCmHandlePublicProperties(*_) >> cmHandlesWithMatchingPublicProperties
+            partiallyMockedCmHandleQueries.queryCmHandleAdditionalProperties(*_) >> cmHandlesWithMatchingPrivateProperties
+        when: 'the query executed'
+            def result = objectUnderTestWithPartiallyMockedQueries.queryCmHandleIdsForInventory(cmHandleQueryParameters)
+        then: 'the expected number of results are returned.'
+            assert result.size() == expectedCmHandleIdsSize
+        where: 'the following data is used'
+            scenario                                          | conditionName                | cmHandlesWithMatchingPublicProperties | cmHandlesWithMatchingPrivateProperties || expectedCmHandleIdsSize
+            'all properties, only public matching'            | 'hasAllProperties'           | ['h1', 'h2']                          | null                                   || 2
+            'all properties, no matching cm handles'          | 'hasAllProperties'           | []                                    | []                                     || 0
+            'additional properties, some matching cm handles' | 'hasAllAdditionalProperties' | []                                    | ['h1', 'h2']                           || 2
+            'additional properties, no matching cm handles'   | 'hasAllAdditionalProperties' | null                                  | []                                     || 0
+    }
+
+    def 'Retrieve CMHandleIds by different DMI properties with #scenario.' () {
+        given: 'a query object created with dmi plugin as condition'
+            def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
+            def conditionProperties = createConditionProperties('cmHandleWithDmiPlugin', [['some-key': 'some-value']])
+            cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties])
+        and: 'the inventoryPersistence returns different CmHandleIds'
+            partiallyMockedCmHandleQueries.getCmHandleIdsByDmiPluginIdentifier(*_) >> cmHandleQueryResult
+        when: 'the query executed'
+            def result = objectUnderTestWithPartiallyMockedQueries.queryCmHandleIdsForInventory(cmHandleQueryParameters)
+        then: 'the expected number of results are returned.'
+            assert result.size() == expectedCmHandleIdsSize
+        where: 'the following data is used'
+            scenario       | cmHandleQueryResult || expectedCmHandleIdsSize
+            'some matches' | ['h1','h2']         || 2
+            'no matches'   | []                  || 0
+    }
+
+    def 'Combine two query results where #scenario.'() {
+        when: 'two query results in the form of a map of NcmpServiceCmHandles are combined into a single query result'
+            def result = objectUnderTest.combineCmHandleQueryResults(firstQuery, secondQuery)
+        then: 'the returned result is the same as the expected result'
+            result == expectedResult
+        where:
+            scenario                                                     | firstQuery              | secondQuery             || expectedResult
+            'two queries with unique and non unique entries exist'       | ['PNFDemo', 'PNFDemo2'] | ['PNFDemo', 'PNFDemo3'] || ['PNFDemo']
+            'the first query contains entries and second query is empty' | ['PNFDemo', 'PNFDemo2'] | []                      || []
+            'the second query contains entries and first query is empty' | []                      | ['PNFDemo', 'PNFDemo3'] || []
+            'the first query contains entries and second query is null'  | ['PNFDemo', 'PNFDemo2'] | null                    || ['PNFDemo', 'PNFDemo2']
+            'the second query contains entries and first query is null'  | null                    | ['PNFDemo', 'PNFDemo3'] || ['PNFDemo', 'PNFDemo3']
+            'both queries are empty'                                     | []                      | []                      || []
+            'both queries are null'                                      | null                    | null                    || null
+    }
+
+    def createConditionProperties(String conditionName, List<Map<String, String>> conditionParameters) {
+        return new ConditionProperties(conditionName : conditionName, conditionParameters : conditionParameters)
+    }
+
+    def static createDataNodeList(dataNodeIds) {
+        def dataNodes =[]
+        dataNodeIds.each{ dataNodes << new DataNode(xpath: "/dmi-registry/cm-handles[@id='${it}']", leaves: ['id':it]) }
+        return dataNodes
+    }
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceSpec.groovy
deleted file mode 100644 (file)
index 05856d0..0000000
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- *  ============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.ncmp.api.impl
-
-import org.onap.cps.cpspath.parser.PathParsingException
-import org.onap.cps.ncmp.api.inventory.CmHandleQueries
-import org.onap.cps.ncmp.api.inventory.CmHandleQueriesImpl
-import org.onap.cps.ncmp.api.inventory.InventoryPersistence
-import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
-import org.onap.cps.spi.FetchDescendantsOption
-import org.onap.cps.spi.exceptions.DataInUseException
-import org.onap.cps.spi.exceptions.DataValidationException
-import org.onap.cps.spi.model.ConditionProperties
-import org.onap.cps.spi.model.DataNode
-import spock.lang.Specification
-import java.util.stream.Collectors
-
-class NetworkCmProxyCmHandlerQueryServiceSpec extends Specification {
-
-    def cmHandleQueries = Mock(CmHandleQueries)
-    def partiallyMockedCmHandleQueries = Spy(CmHandleQueriesImpl)
-    def mockInventoryPersistence = Mock(InventoryPersistence)
-
-    def static someCmHandleDataNode = new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'some-cmhandle-id\']', leaves: ['id':'some-cmhandle-id'])
-    def dmiRegistry = new DataNode(xpath: '/dmi-registry', childDataNodes: createDataNodeList(['PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4']))
-
-    static def queryResultCmHandleMap = createCmHandleMap(['H1', 'H2'])
-
-    def objectUnderTest = new NetworkCmProxyCmHandlerQueryServiceImpl(cmHandleQueries, mockInventoryPersistence)
-    def objectUnderTestSpy = new NetworkCmProxyCmHandlerQueryServiceImpl(partiallyMockedCmHandleQueries, mockInventoryPersistence)
-
-    def 'Retrieve cm handles with cpsPath when combined with no Module Query.'() {
-        given: 'a cmHandleWithCpsPath condition property'
-            def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
-            def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/some/cps/path']])
-            cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties])
-        and: 'cmHandleQueries returns a non null query result'
-            cmHandleQueries.queryCmHandleDataNodesByCpsPath('/some/cps/path', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> [new DataNode(leaves: ['id':'some-cmhandle-id'])]
-        and: 'CmHandleQueries returns cmHandles with the relevant query result'
-            cmHandleQueries.combineCmHandleQueries(*_) >> ['PNFDemo1': new NcmpServiceCmHandle(cmHandleId: 'PNFDemo1'), 'PNFDemo3': new NcmpServiceCmHandle(cmHandleId: 'PNFDemo3')]
-        when: 'the query is executed for both cm handle ids and details'
-            def returnedCmHandlesJustIds = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters)
-            def returnedCmHandlesWithData = objectUnderTest.queryCmHandles(cmHandleQueryParameters)
-        then: 'the correct expected cm handles ids are returned'
-            returnedCmHandlesJustIds == ['PNFDemo1', 'PNFDemo3'] as Set
-        and: 'the correct ncmp service cm handles are returned'
-            returnedCmHandlesWithData.stream().map(CmHandle -> CmHandle.cmHandleId).collect(Collectors.toSet()) == ['PNFDemo1', 'PNFDemo3'] as Set
-    }
-
-    def 'Retrieve cm handles with cpsPath where #scenario.'() {
-        given: 'a cmHandleWithCpsPath condition property'
-            def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
-            def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/some/cps/path']])
-            cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties])
-        and: 'cmHandleQueries throws a path parsing exception'
-            cmHandleQueries.queryCmHandleDataNodesByCpsPath('/some/cps/path', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> { throw thrownException }
-        when: 'the query is executed for both cm handle ids and details'
-            objectUnderTest.queryCmHandleIds(cmHandleQueryParameters)
-            objectUnderTest.queryCmHandles(cmHandleQueryParameters)
-        then: 'a data validation exception is thrown'
-            thrown(expectedException)
-        where: 'the following data is used'
-            scenario                           | thrownException                                          || expectedException
-            'a PathParsingException is thrown' | new PathParsingException('some message', 'some details') || DataValidationException
-            'any other Exception is thrown'    | new DataInUseException('some message', 'some details')   || DataInUseException
-    }
-
-    def 'Query cm handles with public properties when combined with empty modules query result.'() {
-        given: 'a public properties condition property'
-            def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
-            def conditionProperties = createConditionProperties('hasAllProperties', [['some-property-key': 'some-property-value']])
-            cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties])
-        and: 'CmHandleQueries returns cmHandles with the relevant query result'
-            cmHandleQueries.combineCmHandleQueries(*_) >> ['PNFDemo1': new NcmpServiceCmHandle(cmHandleId: 'PNFDemo1'), 'PNFDemo3': new NcmpServiceCmHandle(cmHandleId: 'PNFDemo3')]
-        when: 'the query is executed for both cm handle ids and details'
-            def returnedCmHandlesJustIds = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters)
-            def returnedCmHandlesWithData = objectUnderTest.queryCmHandles(cmHandleQueryParameters)
-        then: 'the correct expected cm handles ids are returned'
-            returnedCmHandlesJustIds == ['PNFDemo1', 'PNFDemo3'] as Set
-        and: 'the correct cm handle data objects are returned'
-            returnedCmHandlesWithData.stream().map(dataNode -> dataNode.cmHandleId).collect(Collectors.toSet()) == ['PNFDemo1', 'PNFDemo3'] as Set
-    }
-
-    def 'Retrieve cm handles with module names when #scenario from query.'() {
-        given: 'a modules condition property'
-            def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
-            def conditionProperties = createConditionProperties('hasAllModules', [['moduleName': 'some-module-name']])
-            cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties])
-        and: 'null is returned from the state and public property queries'
-            cmHandleQueries.combineCmHandleQueries(*_) >> null
-        and: '#scenario from the modules query'
-            mockInventoryPersistence.getCmHandleIdsWithGivenModules(*_) >> cmHandleIdsFromService
-        and: 'the same cmHandles are returned from the persistence service layer'
-            cmHandleIdsFromService.size() * mockInventoryPersistence.getDataNode(*_) >> returnedCmHandles
-        when: 'the query is executed for both cm handle ids and details'
-            def returnedCmHandlesJustIds = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters)
-            def returnedCmHandlesWithData = objectUnderTest.queryCmHandles(cmHandleQueryParameters)
-        then: 'the correct expected cm handles ids are returned'
-            returnedCmHandlesJustIds == cmHandleIdsFromService as Set
-        and: 'the correct cm handle data objects are returned'
-            returnedCmHandlesWithData.stream().map(dataNode -> dataNode.cmHandleId).collect(Collectors.toSet()) == cmHandleIdsFromService as Set
-        where: 'the following data is used'
-            scenario                  | cmHandleIdsFromService | returnedCmHandles
-            'One anchor returned'     | ['some-cmhandle-id']   | someCmHandleDataNode
-            'No anchors are returned' | []                     | null
-    }
-
-    def 'Retrieve cm handles with combined queries when #scenario.'() {
-        given: 'all condition properties used'
-            def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
-            def conditionPubProps = createConditionProperties('hasAllProperties', [['some-property-key': 'some-property-value']])
-            def conditionModules = createConditionProperties('hasAllModules', [['moduleName': 'some-module-name']])
-            def conditionState = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/some/cps/path']])
-            cmHandleQueryParameters.setCmHandleQueryParameters([conditionPubProps, conditionModules, conditionState])
-        and: 'cmHandles are returned from the state and public property combined queries'
-            cmHandleQueries.combineCmHandleQueries(*_) >> combinedQueryMap
-        and: 'cmHandles are returned from the module names query'
-            mockInventoryPersistence.getCmHandleIdsWithGivenModules(['some-module-name']) >> anchorsForModuleQuery
-        and: 'cmHandleQueries returns a datanode result'
-            2 * cmHandleQueries.queryCmHandleDataNodesByCpsPath(*_) >> [someCmHandleDataNode]
-        when: 'the query is executed for both cm handle ids and details'
-            def returnedCmHandlesJustIds = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters)
-            def returnedCmHandlesWithData = objectUnderTest.queryCmHandles(cmHandleQueryParameters)
-        then: 'the correct expected cm handles ids are returned'
-            returnedCmHandlesJustIds == expectedCmHandleIds as Set
-        and: 'the correct cm handle data objects are returned'
-            returnedCmHandlesWithData.stream().map(dataNode -> dataNode.cmHandleId).collect(Collectors.toSet()) == expectedCmHandleIds as Set
-        where: 'the following data is used'
-            scenario                                 | combinedQueryMap                                                                                                           | anchorsForModuleQuery    || expectedCmHandleIds
-            'combined and modules queries intersect' | ['PNFDemo1': new NcmpServiceCmHandle(cmHandleId: 'PNFDemo1')]                                                              | ['PNFDemo1', 'PNFDemo2'] || ['PNFDemo1']
-            'only module query results exist'        | [:]                                                                                                                        | ['PNFDemo1', 'PNFDemo2'] || []
-            'only combined query results exist'      | ['PNFDemo1': new NcmpServiceCmHandle(cmHandleId: 'PNFDemo1'), 'PNFDemo2': new NcmpServiceCmHandle(cmHandleId: 'PNFDemo2')] | []                       || []
-            'neither queries return results'         | [:]                                                                                                                        | []                       || []
-            'none intersect'                         | ['PNFDemo1': new NcmpServiceCmHandle(cmHandleId: 'PNFDemo1')]                                                              | ['PNFDemo2']             || []
-    }
-
-    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 with just ids'
-            mockInventoryPersistence.getDataNode("/dmi-registry", FetchDescendantsOption.FETCH_DIRECT_CHILDREN_ONLY) >> dmiRegistry
-        and: 'the inventory persistence returns the dmi registry datanode with data'
-            mockInventoryPersistence.getDataNode("/dmi-registry") >> dmiRegistry
-        when: 'the query is executed for both cm handle ids and details'
-            def returnedCmHandlesJustIds = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters)
-            def returnedCmHandlesWithData = objectUnderTest.queryCmHandles(cmHandleQueryParameters)
-        then: 'the correct expected cm handles are returned'
-            returnedCmHandlesJustIds == ['PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4'] as Set
-            returnedCmHandlesWithData.stream().map(d -> d.cmHandleId).collect(Collectors.toSet()) == ['PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4'] as Set
-    }
-
-
-    def 'Retrieve all CMHandleIds for empty query parameters' () {
-        given: 'We query without any parameters'
-            def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
-        and: 'the inventoryPersistence returns all four CmHandleIds'
-            mockInventoryPersistence.getDataNode(*_) >> dmiRegistry
-        when: 'the query executed'
-            def resultSet = objectUnderTest.queryCmHandleIdsForInventory(cmHandleQueryParameters)
-        then: 'the size of the result list equals the size of all cmHandleIds.'
-            resultSet.size() == 4
-    }
-
-    def 'Retrieve CMHandleIds when #scenario.' () {
-        given: 'a query object created with #condition'
-            def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
-            def conditionProperties = createConditionProperties(conditionName, [['some-key': 'some-value']])
-            cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties])
-        and: 'the inventoryPersistence returns different CmHandleIds'
-            partiallyMockedCmHandleQueries.queryCmHandlePublicProperties(*_) >> cmHandlesWithMatchingPublicProperties
-            partiallyMockedCmHandleQueries.queryCmHandleAdditionalProperties(*_) >> cmHandlesWithMatchingPrivateProperties
-        when: 'the query executed'
-            def result = objectUnderTestSpy.queryCmHandleIdsForInventory(cmHandleQueryParameters)
-        then: 'the expected number of results are returned.'
-            assert result.size() == expectedCmHandleIdsSize
-        where: 'the following data is used'
-            scenario                                          | conditionName                | cmHandlesWithMatchingPublicProperties | cmHandlesWithMatchingPrivateProperties || expectedCmHandleIdsSize
-            'all properties, only public matching'            | 'hasAllProperties'           | queryResultCmHandleMap                |  null                                  || 2
-            'all properties, no matching cm handles'          | 'hasAllProperties'           | [:]                                   |  [:]                                   || 0
-            'additional properties, some matching cm handles' | 'hasAllAdditionalProperties' | [:]                                   | queryResultCmHandleMap                 || 2
-            'additional properties, no matching cm handles'   | 'hasAllAdditionalProperties' | null                                  |  [:]                                   || 0
-    }
-
-    def 'Retrieve CMHandleIds by different DMI properties with #scenario.' () {
-        given: 'a query object created with dmi plugin as condition'
-            def cmHandleQueryParameters = new CmHandleQueryServiceParameters()
-            def conditionProperties = createConditionProperties('cmHandleWithDmiPlugin', [['some-key': 'some-value']])
-            cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties])
-        and: 'the inventoryPersistence returns different CmHandleIds'
-            partiallyMockedCmHandleQueries.getCmHandlesByDmiPluginIdentifier(*_) >> cmHandleQueryResult
-        when: 'the query executed'
-            def result = objectUnderTestSpy.queryCmHandleIdsForInventory(cmHandleQueryParameters)
-        then: 'the expected number of results are returned.'
-            assert result.size() == expectedCmHandleIdsSize
-        where: 'the following data is used'
-            scenario       | cmHandleQueryResult             || expectedCmHandleIdsSize
-            'some matches' | queryResultCmHandleMap.values() || 2
-            'no matches'   | []                              || 0
-    }
-
-    static def createCmHandleMap(cmHandleIds) {
-        def cmHandleMap = [:]
-        cmHandleIds.each{ cmHandleMap[it] = new NcmpServiceCmHandle(cmHandleId : it) }
-        return cmHandleMap
-    }
-
-    def createConditionProperties(String conditionName, List<Map<String, String>> conditionParameters) {
-        return new ConditionProperties(conditionName : conditionName, conditionParameters : conditionParameters)
-    }
-
-    def static createDataNodeList(dataNodeIds) {
-        def dataNodes =[]
-        dataNodeIds.each{ dataNodes << new DataNode(xpath: "/dmi-registry/cm-handles[@id='${it}']", leaves: ['id':it]) }
-        return dataNodes
-    }
-}
index 272030b..f12969d 100644 (file)
@@ -25,7 +25,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
 import com.hazelcast.map.IMap
 import org.onap.cps.api.CpsDataService
 import org.onap.cps.api.CpsModuleService
-import org.onap.cps.ncmp.api.NetworkCmProxyCmHandlerQueryService
+import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService
 import org.onap.cps.ncmp.api.impl.event.lcm.LcmEventsCmHandleStateHandler
 import org.onap.cps.ncmp.api.impl.exception.DmiRequestException
 import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations
@@ -49,7 +49,6 @@ import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Registra
 import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.CM_HANDLE_INVALID_ID
 import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.UNKNOWN_ERROR
 import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status
-import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED
 
 class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
 
@@ -62,7 +61,7 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
     def mockNetworkCmProxyDataServicePropertyHandler = Mock(NetworkCmProxyDataServicePropertyHandler)
     def mockInventoryPersistence = Mock(InventoryPersistence)
     def mockCmhandleQueries = Mock(CmHandleQueries)
-    def stubbedNetworkCmProxyCmHandlerQueryService = Stub(NetworkCmProxyCmHandlerQueryService)
+    def stubbedNetworkCmProxyCmHandlerQueryService = Stub(NetworkCmProxyCmHandleQueryService)
     def mockLcmEventsCmHandleStateHandler = Mock(LcmEventsCmHandleStateHandler)
     def mockCpsDataService = Mock(CpsDataService)
     def mockModuleSyncStartedOnCmHandles = Mock(IMap<String, Object>)
index 578f7f3..4acd249 100644 (file)
@@ -1,8 +1,9 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2022 Nordix Foundation
+ *  Copyright (C) 2021-2023 Nordix Foundation
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2021-2022 Bell Canada
+ *  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.
@@ -23,7 +24,7 @@
 package org.onap.cps.ncmp.api.impl
 
 import com.hazelcast.map.IMap
-import org.onap.cps.ncmp.api.NetworkCmProxyCmHandlerQueryService
+import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService
 import org.onap.cps.ncmp.api.impl.event.lcm.LcmEventsCmHandleStateHandler
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
 import org.onap.cps.ncmp.api.inventory.CmHandleQueries
@@ -65,7 +66,7 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
     def mockInventoryPersistence = Mock(InventoryPersistence)
     def mockCmHandleQueries = Mock(CmHandleQueries)
     def mockDmiPluginRegistration = Mock(DmiPluginRegistration)
-    def mockCpsCmHandlerQueryService = Mock(NetworkCmProxyCmHandlerQueryService)
+    def mockCpsCmHandlerQueryService = Mock(NetworkCmProxyCmHandleQueryService)
     def mockLcmEventsCmHandleStateHandler = Mock(LcmEventsCmHandleStateHandler)
     def stubModuleSyncStartedOnCmHandles = Stub(IMap<String, Object>)
 
@@ -89,11 +90,11 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
 
     def cmHandleXPath = "/dmi-registry/cm-handles[@id='testCmHandle']"
 
-    def dataNode = new DataNode(leaves: ['id': 'some-cm-handle', 'dmi-service-name': 'testDmiService'])
+    def dataNode = [new DataNode(leaves: ['id': 'some-cm-handle', 'dmi-service-name': 'testDmiService'])]
 
     def 'Write resource data for pass-through running from DMI using POST.'() {
         given: 'cpsDataService returns valid datanode'
-            mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
+            mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
                 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
         when: 'write resource data is called'
             objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
@@ -107,7 +108,7 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
 
     def 'Get resource data for pass-through operational from DMI.'() {
         given: 'get data node is called'
-            mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
+            mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
                 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
         and: 'get resource data from DMI is called'
             mockDmiDataOperations.getResourceDataFromDmi(
@@ -129,7 +130,7 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
 
     def 'Get resource data for pass-through running from DMI.'() {
         given: 'cpsDataService returns valid data node'
-            mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
+            mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
                 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
         and: 'DMI returns valid response and data'
             mockDmiDataOperations.getResourceDataFromDmi('testCmHandle',
@@ -233,7 +234,7 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
 
     def 'Update resource data for pass-through running from dmi using POST #scenario DMI properties.'() {
         given: 'cpsDataService returns valid datanode'
-            mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
+            mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
                 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
         when: 'get resource data is called'
             objectUnderTest.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
@@ -280,7 +281,7 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
         when: 'execute cm handle search is called'
             def result = objectUnderTest.executeCmHandleIdSearch(cmHandleQueryApiParameters)
         then: 'result is the same collection as returned by the CPS Data Service'
-            assert result == ['cm-handle-id-1'] as Set
+            assert result == ['cm-handle-id-1']
     }
 
     def 'Getting module definitions.'() {
@@ -349,9 +350,7 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
 
     def 'Get all cm handle IDs by DMI plugin identifier.' () {
         given: 'cm handle queries service returns cm handles'
-            1 * mockCmHandleQueries.getCmHandlesByDmiPluginIdentifier('some-dmi-plugin-identifier')
-                    >> [new NcmpServiceCmHandle(cmHandleId: 'cm-handle-1'),
-                        new NcmpServiceCmHandle(cmHandleId: 'cm-handle-2')]
+            1 * mockCmHandleQueries.getCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier') >> ['cm-handle-1','cm-handle-2']
         when: 'cm handle Ids are requested with dmi plugin identifier'
             def result = objectUnderTest.getAllCmHandleIdsByDmiPluginIdentifier('some-dmi-plugin-identifier')
         then: 'the result size is correct'
index 64461fa..0df61f4 100644 (file)
@@ -2,6 +2,7 @@
  * ============LICENSE_START=======================================================
  * Copyright (C) 2022 Nordix Foundation
  * Modifications Copyright (C) 2022 Bell Canada
+ * 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.
@@ -47,11 +48,11 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification {
                                     new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/additional-properties[@name='additionalProp2']").withLeaves(['name': 'additionalProp2', 'value': 'additionalValue2']).build(),
                                     new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/public-properties[@name='publicProp3']").withLeaves(['name': 'publicProp3', 'value': 'publicValue3']).build(),
                                     new DataNodeBuilder().withXpath("/dmi-registry/cm-handles[@id='${cmHandleId}']/public-properties[@name='publicProp4']").withLeaves(['name': 'publicProp4', 'value': 'publicValue4']).build()]
-    def static cmHandleDataNode = new DataNode(xpath: cmHandleXpath, childDataNodes: propertyDataNodes)
+    def static cmHandleDataNodeAsCollection = [new DataNode(xpath: cmHandleXpath, childDataNodes: propertyDataNodes)]
 
     def 'Update CM Handle Public Properties: #scenario'() {
         given: 'the CPS service return a CM handle'
-            mockInventoryPersistence.getCmHandleDataNode(cmHandleId) >> cmHandleDataNode
+            mockInventoryPersistence.getCmHandleDataNode(cmHandleId) >> cmHandleDataNodeAsCollection
         and: 'an update cm handle request with public properties updates'
             def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: updatedPublicProperties)]
         when: 'update data node leaves is called with the update request'
@@ -73,7 +74,7 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification {
 
     def 'Update DMI Properties: #scenario'() {
         given: 'the CPS service return a CM handle'
-            mockInventoryPersistence.getCmHandleDataNode(cmHandleId) >> cmHandleDataNode
+            mockInventoryPersistence.getCmHandleDataNode(cmHandleId) >> cmHandleDataNodeAsCollection
         and: 'an update cm handle request with DMI properties updates'
             def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, dmiProperties: updatedDmiProperties)]
         when: 'update data node leaves is called with the update request'
@@ -97,7 +98,7 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification {
     def 'Update CM Handle Properties, remove all properties: #scenario'() {
         given: 'the CPS service return a CM handle'
             def cmHandleDataNode = new DataNode(xpath: cmHandleXpath, childDataNodes: originalPropertyDataNodes)
-            mockInventoryPersistence.getCmHandleDataNode(cmHandleId) >> cmHandleDataNode
+            mockInventoryPersistence.getCmHandleDataNode(cmHandleId) >> [cmHandleDataNode]
         and: 'an update cm handle request that removes all public properties(existing and non-existing)'
             def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: ['publicProp3': null, 'publicProp4': null])]
         when: 'update data node leaves is called with the update request'
@@ -145,7 +146,8 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification {
                                          new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: ['publicProp1': "value"], dmiProperties: [:]),
                                          new NcmpServiceCmHandle(cmHandleId: cmHandleId, publicProperties: ['publicProp1': "value"], dmiProperties: [:])]
         and: 'data node can be found for 1st and 3rd cm-handle but not for 2nd cm-handle'
-            mockInventoryPersistence.getCmHandleDataNode(*_) >> cmHandleDataNode >> { throw new DataNodeNotFoundException('NCMP-Admin', 'ncmp-dmi-registry') } >> cmHandleDataNode
+            mockInventoryPersistence.getCmHandleDataNode(*_) >> cmHandleDataNodeAsCollection >> {
+                throw new DataNodeNotFoundException('NCMP-Admin', 'ncmp-dmi-registry') } >> cmHandleDataNodeAsCollection
         when: 'update data node leaves is called using correct parameters'
             def cmHandleResponseList = objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest)
         then: 'response has 3 values'
index 2800f5c..fd01a05 100644 (file)
@@ -1,6 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022 Nordix Foundation
+ *  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.
@@ -20,7 +21,6 @@
 
 package org.onap.cps.ncmp.api.inventory
 
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
 import org.onap.cps.spi.CpsDataPersistenceService
 import org.onap.cps.spi.model.DataNode
 import spock.lang.Shared
@@ -45,18 +45,14 @@ class CmHandleQueriesImplSpec extends Specification {
     def static pnfDemo4 = createDataNode('PNFDemo4')
     def static pnfDemo5 = createDataNode('PNFDemo5')
 
-    def static pnfDemoCmHandle = new NcmpServiceCmHandle(cmHandleId: 'PNFDemo')
-    def static pnfDemo2CmHandle = new NcmpServiceCmHandle(cmHandleId: 'PNFDemo2')
-    def static pnfDemo3CmHandle = new NcmpServiceCmHandle(cmHandleId: 'PNFDemo3')
-
     def 'Query CmHandles with public properties query pair.'() {
         given: 'the DataNodes queried for a given cpsPath are returned from the persistence service.'
             mockResponses()
         when: 'a query on cmhandle public properties is performed with a public property pair'
-            def returnedCmHandlesWithData = objectUnderTest.queryCmHandlePublicProperties(publicPropertyPairs)
+            def result = objectUnderTest.queryCmHandlePublicProperties(publicPropertyPairs)
         then: 'the correct cm handle data objects are returned'
-            returnedCmHandlesWithData.keySet().containsAll(expectedCmHandleIds)
-            returnedCmHandlesWithData.keySet().size() == expectedCmHandleIds.size()
+            result.containsAll(expectedCmHandleIds)
+            result.size() == expectedCmHandleIds.size()
         where: 'the following data is used'
             scenario                         | publicPropertyPairs                                                                           || expectedCmHandleIds
             'single property matches'        | ['Contact' : 'newemailforstore@bookstore.com']                                                || ['PNFDemo', 'PNFDemo2', 'PNFDemo4']
@@ -67,41 +63,25 @@ class CmHandleQueriesImplSpec extends Specification {
 
     def 'Query CmHandles using empty public properties query pair.'() {
         when: 'a query on CmHandle public properties is executed using an empty map'
-            def returnedCmHandlesWithData = objectUnderTest.queryCmHandlePublicProperties([:])
+            def result = objectUnderTest.queryCmHandlePublicProperties([:])
         then: 'no cm handles are returned'
-            returnedCmHandlesWithData.keySet().size() == 0
+            result.size() == 0
     }
 
     def 'Query CmHandles using empty private properties query pair.'() {
         when: 'a query on CmHandle private properties is executed using an empty map'
-            def returnedCmHandlesWithData = objectUnderTest.queryCmHandleAdditionalProperties([:])
+            def result = objectUnderTest.queryCmHandleAdditionalProperties([:])
         then: 'no cm handles are returned'
-            returnedCmHandlesWithData.keySet().size() == 0
+            result.size() == 0
     }
 
     def 'Query CmHandles by a private field\'s value.'() {
         given: 'a data node exists with a certain additional-property'
             cpsDataPersistenceService.queryDataNodes(_, _, dataNodeWithPrivateField, _) >> [pnfDemo5]
         when: 'a query on CmHandle private properties is executed using a map'
-            def returnedCmHandlesWithData = objectUnderTest.queryCmHandleAdditionalProperties(['Contact3': 'newemailforstore3@bookstore.com'])
+            def result = objectUnderTest.queryCmHandleAdditionalProperties(['Contact3': 'newemailforstore3@bookstore.com'])
         then: 'one cm handle is returned'
-            returnedCmHandlesWithData.keySet().size() == 1
-    }
-
-    def 'Combine two query results where #scenario.'() {
-        when: 'two query results in the form of a map of NcmpServiceCmHandles are combined into a single query result'
-            def result = objectUnderTest.combineCmHandleQueries(firstQuery, secondQuery)
-        then: 'the returned result is the same as the expected result'
-            result == expectedResult
-        where:
-            scenario                                                     | firstQuery                                                 | secondQuery                                                || expectedResult
-            'two queries with unique and non unique entries exist'       | ['PNFDemo': pnfDemoCmHandle, 'PNFDemo2': pnfDemo2CmHandle] | ['PNFDemo': pnfDemoCmHandle, 'PNFDemo3': pnfDemo3CmHandle] || ['PNFDemo': pnfDemoCmHandle]
-            'the first query contains entries and second query is empty' | ['PNFDemo': pnfDemoCmHandle, 'PNFDemo2': pnfDemo2CmHandle] | [:]                                                        || [:]
-            'the second query contains entries and first query is empty' | [:]                                                        | ['PNFDemo': pnfDemoCmHandle, 'PNFDemo3': pnfDemo3CmHandle] || [:]
-            'the first query contains entries and second query is null'  | ['PNFDemo': pnfDemoCmHandle, 'PNFDemo2': pnfDemo2CmHandle] | null                                                       || ['PNFDemo': pnfDemoCmHandle, 'PNFDemo2': pnfDemo2CmHandle]
-            'the second query contains entries and first query is null'  | null                                                       | ['PNFDemo': pnfDemoCmHandle, 'PNFDemo3': pnfDemo3CmHandle] || ['PNFDemo': pnfDemoCmHandle, 'PNFDemo3': pnfDemo3CmHandle]
-            'both queries are empty'                                     | [:]                                                        | [:]                                                        || [:]
-            'both queries are null'                                      | null                                                       | null                                                       || null
+            result.size() == 1
     }
 
     def 'Get CmHandles by it\'s state.'() {
@@ -120,8 +100,8 @@ class CmHandleQueriesImplSpec extends Specification {
         given: 'a cm handle state to compare'
             def cmHandleState = state
         and: 'the persistence service returns a list of data nodes'
-            cpsDataPersistenceService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
-                    '/dmi-registry/cm-handles[@id=\'some-cm-handle\']/state', OMIT_DESCENDANTS) >> new DataNode(leaves: ['cm-handle-state': 'READY'])
+            cpsDataPersistenceService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
+                    '/dmi-registry/cm-handles[@id=\'some-cm-handle\']/state', OMIT_DESCENDANTS) >> [new DataNode(leaves: ['cm-handle-state': 'READY'])]
         when: 'cm handles are compared by state'
             def result = objectUnderTest.cmHandleHasState('some-cm-handle', cmHandleState)
         then: 'the returned result matches the expected result from the persistence service'
@@ -136,8 +116,8 @@ class CmHandleQueriesImplSpec extends Specification {
         given: 'a cm handle state to query'
             def cmHandleState = CmHandleState.READY
         and: 'cps data service returns a list of data nodes'
-            cpsDataPersistenceService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
-                '/dmi-registry/cm-handles[@id=\'some-cm-handle\']/state', OMIT_DESCENDANTS) >> new DataNode(leaves: ['cm-handle-state': 'READY'])
+            cpsDataPersistenceService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
+                '/dmi-registry/cm-handles[@id=\'some-cm-handle\']/state', OMIT_DESCENDANTS) >> [new DataNode(leaves: ['cm-handle-state': 'READY'])]
         when: 'cm handles are fetched by state and id'
             def result = objectUnderTest.getCmHandleState('some-cm-handle')
         then: 'the returned result is a list of data nodes returned by cps data service'
@@ -174,11 +154,11 @@ class CmHandleQueriesImplSpec extends Specification {
         given: 'the DataNodes queried for a given cpsPath are returned from the persistence service.'
             mockResponses()
         when: 'cm Handles are fetched for a given dmi plugin identifier'
-            def result = objectUnderTest.getCmHandlesByDmiPluginIdentifier('my-dmi-plugin-identifier')
+            def result = objectUnderTest.getCmHandleIdsByDmiPluginIdentifier('my-dmi-plugin-identifier')
         then: 'result is the correct size'
             assert result.size() == 3
         and: 'result contains the correct cm handles'
-            assert result.cmHandleId.containsAll('PNFDemo', 'PNFDemo2', 'PNFDemo4')
+            assert result.containsAll('PNFDemo', 'PNFDemo2', 'PNFDemo4')
     }
 
     void mockResponses() {
index d01df3a..efe6f00 100644 (file)
@@ -2,6 +2,7 @@
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2022-2023 Nordix Foundation
  *  Modifications Copyright (C) 2022 Bell Canada
+ *  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.
@@ -39,7 +40,6 @@ import spock.lang.Specification
 import java.time.OffsetDateTime
 import java.time.ZoneOffset
 import java.time.format.DateTimeFormatter
-import java.util.stream.Collectors
 
 import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NO_TIMESTAMP
 import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
@@ -85,7 +85,7 @@ class InventoryPersistenceImplSpec extends Specification {
     def "Retrieve CmHandle using datanode with #scenario."() {
         given: 'the cps data service returns a data node from the DMI registry'
             def dataNode = new DataNode(childDataNodes:childDataNodes, leaves: leaves)
-            mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry', xpath, INCLUDE_ALL_DESCENDANTS) >> dataNode
+            mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry', xpath, INCLUDE_ALL_DESCENDANTS) >> [dataNode]
         when: 'retrieving the yang modelled cm handle'
             def result = objectUnderTest.getYangModelCmHandle(cmHandleId)
         then: 'the result has the correct id and service names'
@@ -112,7 +112,7 @@ class InventoryPersistenceImplSpec extends Specification {
     def "Handling missing service names as null."() {
         given: 'the cps data service returns a data node from the DMI registry with empty child and leaf attributes'
             def dataNode = new DataNode(childDataNodes:[], leaves: [:])
-            mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry', xpath, INCLUDE_ALL_DESCENDANTS) >> dataNode
+            mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry', xpath, INCLUDE_ALL_DESCENDANTS) >> [dataNode]
         when: 'retrieving the yang modelled cm handle'
             def result = objectUnderTest.getYangModelCmHandle(cmHandleId)
         then: 'the service names are returned as null'
@@ -126,7 +126,7 @@ class InventoryPersistenceImplSpec extends Specification {
     def "Retrieve multiple YangModelCmHandles"() {
         given: 'the cps data service returns 2 data nodes from the DMI registry'
             def dataNodes = [new DataNode(xpath: xpath), new DataNode(xpath: xpath2)]
-            mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry', [xpath, xpath2] , INCLUDE_ALL_DESCENDANTS) >> dataNodes
+            mockCpsDataService.getDataNodesForMultipleXpaths('NCMP-Admin', 'ncmp-dmi-registry', [xpath, xpath2] , INCLUDE_ALL_DESCENDANTS) >> dataNodes
         when: 'retrieving the yang modelled cm handle'
             def results = objectUnderTest.getYangModelCmHandles([cmHandleId, cmHandleId2])
         then: 'verify both have returned and cmhandleIds are correct'
@@ -139,8 +139,8 @@ class InventoryPersistenceImplSpec extends Specification {
             def cmHandleId = 'Some-Cm-Handle'
             def dataNode = new DataNode(leaves: ['cm-handle-state': 'ADVISED'])
         and: 'cps data service returns a valid data node'
-            mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
-                    '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle\']/state', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
+            mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
+                    '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle\']/state', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> [dataNode]
         when: 'get cm handle state is invoked'
             def result = objectUnderTest.getCmHandleState(cmHandleId)
         then: 'result has returned the correct cm handle state'
@@ -260,7 +260,7 @@ class InventoryPersistenceImplSpec extends Specification {
         when: 'the method to get data nodes is called'
             objectUnderTest.getDataNode('sample xPath')
         then: 'the data persistence service method to get data node is invoked once'
-            1 * mockCpsDataService.getDataNode('NCMP-Admin','ncmp-dmi-registry','sample xPath', INCLUDE_ALL_DESCENDANTS)
+            1 * mockCpsDataService.getDataNodes('NCMP-Admin','ncmp-dmi-registry','sample xPath', INCLUDE_ALL_DESCENDANTS)
     }
 
     def 'Get cmHandle data node'() {
@@ -269,7 +269,7 @@ class InventoryPersistenceImplSpec extends Specification {
         when: 'the method to get data nodes is called'
             objectUnderTest.getCmHandleDataNode('sample cmHandleId')
         then: 'the data persistence service method to get cmHandle data node is invoked once with expected xPath'
-            1 * mockCpsDataService.getDataNode('NCMP-Admin','ncmp-dmi-registry',expectedXPath, INCLUDE_ALL_DESCENDANTS)
+            1 * mockCpsDataService.getDataNodes('NCMP-Admin','ncmp-dmi-registry',expectedXPath, INCLUDE_ALL_DESCENDANTS)
     }
 
     def 'Get CM handles that has given module names'() {
index f66f57b..080b97a 100755 (executable)
@@ -32,7 +32,7 @@
 
     <groupId>org.onap.cps</groupId>
     <artifactId>cps-parent</artifactId>
-    <version>3.2.3-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
     <packaging>pom</packaging>
 
     <properties>
index 3a468ee..c95d6a8 100644 (file)
@@ -23,7 +23,7 @@
     <parent>
         <groupId>org.onap.cps</groupId>
         <artifactId>cps-parent</artifactId>
-        <version>3.2.3-SNAPSHOT</version>
+        <version>3.2.4-SNAPSHOT</version>
         <relativePath>../cps-parent/pom.xml</relativePath>
     </parent>
 
index c9df8df..3985455 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2022 Nordix Foundation
+ *  Copyright (C) 2021-2023 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -80,4 +80,13 @@ public class CpsPathQuery {
         return textFunctionConditionLeafName != null;
     }
 
+    /**
+     * Returns boolean indicating xpath is an absolute path to a list element.
+     *
+     * @return true if xpath is an absolute path to a list element
+     */
+    public boolean isPathToListElement() {
+        return cpsPathPrefixType == ABSOLUTE && hasLeafConditions();
+    }
+
 }
index 60f0e2e..bde9b06 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022 Nordix Foundation
+ *  Copyright (C) 2022-2023 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -20,8 +20,6 @@
 
 package org.onap.cps.cpspath.parser;
 
-import static org.onap.cps.cpspath.parser.CpsPathPrefixType.ABSOLUTE;
-
 import java.util.List;
 import lombok.AccessLevel;
 import lombok.Getter;
@@ -75,7 +73,7 @@ public class CpsPathUtil {
      */
     public static boolean isPathToListElement(final String xpathSource) {
         final CpsPathQuery cpsPathQuery = getCpsPathBuilder(xpathSource).build();
-        return cpsPathQuery.getCpsPathPrefixType() == ABSOLUTE && cpsPathQuery.hasLeafConditions();
+        return cpsPathQuery.isPathToListElement();
     }
 
     /**
index 61663ab..ad0c299 100644 (file)
@@ -46,4 +46,4 @@ nodeByDataspaceAndAnchor:
         $ref: 'components.yml#/components/responses/Forbidden'
       '500':
         $ref: 'components.yml#/components/responses/InternalServerError'
-    x-codegen-request-body-name: xpath
+    x-codegen-request-body-name: xpath
\ No newline at end of file
index 7f1ff59..be104b1 100755 (executable)
@@ -28,7 +28,7 @@
     <parent>
         <groupId>org.onap.cps</groupId>
         <artifactId>cps-parent</artifactId>
-        <version>3.2.3-SNAPSHOT</version>
+        <version>3.2.4-SNAPSHOT</version>
         <relativePath>../cps-parent/pom.xml</relativePath>
     </parent>
 
index 3a9c764..80cfb8c 100755 (executable)
@@ -3,7 +3,7 @@
  *  Copyright (C) 2020-2022 Bell Canada.
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2021-2022 Nordix Foundation
- *  Modifications Copyright (C) 2023 TechMahindra Ltd.
+ *  Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
  *  Modifications Copyright (C) 2022 Deutsche Telekom AG
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -26,6 +26,10 @@ package org.onap.cps.rest.controller;
 
 import java.time.OffsetDateTime;
 import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
 import javax.validation.ValidationException;
 import lombok.RequiredArgsConstructor;
 import org.apache.commons.lang3.StringUtils;
@@ -97,21 +101,27 @@ public class DataRestController implements CpsDataApi {
         final String anchorName, final String xpath, final Boolean includeDescendants) {
         final FetchDescendantsOption fetchDescendantsOption = Boolean.TRUE.equals(includeDescendants)
             ? FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS : FetchDescendantsOption.OMIT_DESCENDANTS;
-        final DataNode dataNode = cpsDataService.getDataNode(dataspaceName, anchorName, xpath,
-            fetchDescendantsOption);
-        final String prefix = prefixResolver.getPrefix(dataspaceName, anchorName, xpath);
+        final DataNode dataNode = cpsDataService.getDataNodes(dataspaceName, anchorName, xpath,
+            fetchDescendantsOption).iterator().next();
+        final String prefix = prefixResolver.getPrefix(dataspaceName, anchorName, dataNode.getXpath());
         return new ResponseEntity<>(DataMapUtils.toDataMapWithIdentifier(dataNode, prefix), HttpStatus.OK);
     }
 
     @Override
     public ResponseEntity<Object> getNodeByDataspaceAndAnchorV2(final String dataspaceName, final String anchorName,
-        final String xpath, final String fetchDescendantsOptionAsString) {
+                                                                final String xpath,
+                                                                final String fetchDescendantsOptionAsString) {
         final FetchDescendantsOption fetchDescendantsOption =
-            FetchDescendantsOption.getFetchDescendantsOption(fetchDescendantsOptionAsString);
-        final DataNode dataNode = cpsDataService.getDataNode(dataspaceName, anchorName, xpath,
-            fetchDescendantsOption);
-        final String prefix = prefixResolver.getPrefix(dataspaceName, anchorName, xpath);
-        return new ResponseEntity<>(DataMapUtils.toDataMapWithIdentifier(dataNode, prefix), HttpStatus.OK);
+                FetchDescendantsOption.getFetchDescendantsOption(fetchDescendantsOptionAsString);
+        final Collection<DataNode> dataNodes = cpsDataService.getDataNodes(dataspaceName, anchorName, xpath,
+                fetchDescendantsOption);
+        final List<Map<String, Object>> dataMaps = new ArrayList<>(dataNodes.size());
+        for (final DataNode dataNode: dataNodes) {
+            final String prefix = prefixResolver.getPrefix(dataspaceName, anchorName, dataNode.getXpath());
+            final Map<String, Object> dataMap = DataMapUtils.toDataMapWithIdentifier(dataNode, prefix);
+            dataMaps.add(dataMap);
+        }
+        return new ResponseEntity<>(jsonObjectMapper.asJsonString(dataMaps), HttpStatus.OK);
     }
 
     @Override
@@ -162,5 +172,4 @@ public class DataRestController implements CpsDataApi {
                 String.format("observed-timestamp must be in '%s' format", ISO_TIMESTAMP_FORMAT));
         }
     }
-
 }
index 2e303d1..e514b93 100644 (file)
@@ -29,7 +29,7 @@ import org.onap.cps.spi.exceptions.ModelValidationException;
 public class ZipFileSizeValidator {
 
     private static final int THRESHOLD_ENTRIES = 10000;
-    private static int THRESHOLD_SIZE = 100000000;
+    private static int thresholdSize = 100000000;
     private static final double THRESHOLD_RATIO = 40;
     private static final String INVALID_ZIP = "Invalid ZIP archive content.";
 
@@ -71,10 +71,10 @@ public class ZipFileSizeValidator {
      * Validate the total Size and number of entries in the zip.
      */
     public void validateSizeAndEntries() {
-        if (totalUncompressedSizeOfYangFilesInArchive > THRESHOLD_SIZE) {
+        if (totalUncompressedSizeOfYangFilesInArchive > thresholdSize) {
             throw new ModelValidationException(INVALID_ZIP,
                 String.format("The total size of uncompressed yang files exceeds the CPS limit of %s bytes.",
-                    THRESHOLD_SIZE));
+                        thresholdSize));
         }
         if (totalYangFileEntriesInArchive > THRESHOLD_ENTRIES) {
             throw new ModelValidationException(INVALID_ZIP,
index 16d106b..d88a9cd 100755 (executable)
@@ -25,7 +25,9 @@
 package org.onap.cps.rest.controller
 
 import com.fasterxml.jackson.databind.ObjectMapper
+import groovy.json.JsonSlurper
 import org.onap.cps.api.CpsDataService
+import org.onap.cps.spi.FetchDescendantsOption
 import org.onap.cps.spi.model.DataNode
 import org.onap.cps.spi.model.DataNodeBuilder
 import org.onap.cps.utils.ContentType
@@ -68,7 +70,7 @@ class DataRestControllerSpec extends Specification {
     @Value('${rest.api.cps-base-path}')
     def basePath
 
-    def dataNodeBaseEndpoint
+    def dataNodeBaseEndpointV1
     def dataNodeBaseEndpointV2
     def dataspaceName = 'my_dataspace'
     def anchorName = 'my_anchor'
@@ -87,21 +89,25 @@ class DataRestControllerSpec extends Specification {
     def expectedXmlData = '<?xml version=\'1.0\' encoding=\'UTF-8\'?>\n<bookstore xmlns="org:onap:ccsdk:sample">\n</bookstore>'
 
     @Shared
-    static DataNode dataNodeWithLeavesNoChildren = new DataNodeBuilder().withXpath('/xpath')
+    static DataNode dataNodeWithLeavesNoChildren = new DataNodeBuilder().withXpath('/parent-1')
         .withLeaves([leaf: 'value', leafList: ['leaveListElement1', 'leaveListElement2']]).build()
 
+    @Shared
+    static DataNode dataNodeWithLeavesNoChildren2 = new DataNodeBuilder().withXpath('/parent-2')
+        .withLeaves([leaf: 'value']).build()
+
     @Shared
     static DataNode dataNodeWithChild = new DataNodeBuilder().withXpath('/parent')
         .withChildDataNodes([new DataNodeBuilder().withXpath("/parent/child").build()]).build()
 
     def setup() {
-        dataNodeBaseEndpoint = "$basePath/v1/dataspaces/$dataspaceName"
+        dataNodeBaseEndpointV1 = "$basePath/v1/dataspaces/$dataspaceName"
         dataNodeBaseEndpointV2 = "$basePath/v2/dataspaces/$dataspaceName"
     }
 
     def 'Create a node: #scenario.'() {
         given: 'endpoint to create a node'
-            def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
+            def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
         when: 'post is invoked with datanode endpoint and json'
             def response =
                 mvc.perform(
@@ -124,7 +130,7 @@ class DataRestControllerSpec extends Specification {
 
     def 'Create a node with observed-timestamp'() {
         given: 'endpoint to create a node'
-            def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
+            def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
         when: 'post is invoked with datanode endpoint and json'
             def response =
                 mvc.perform(
@@ -148,7 +154,7 @@ class DataRestControllerSpec extends Specification {
 
     def 'Create a child node #scenario'() {
         given: 'endpoint to create a node'
-            def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
+            def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
         and: 'parent node xpath'
             def parentNodeXpath = 'some xpath'
         when: 'post is invoked with datanode endpoint and json'
@@ -177,7 +183,7 @@ class DataRestControllerSpec extends Specification {
         given: 'parent node xpath '
             def parentNodeXpath = 'parent node xpath'
         when: 'list-node endpoint is invoked with post (create) operation'
-            def postRequestBuilder = post("$dataNodeBaseEndpoint/anchors/$anchorName/list-nodes")
+            def postRequestBuilder = post("$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes")
                 .contentType(MediaType.APPLICATION_JSON)
                 .param('xpath', parentNodeXpath)
                 .content(requestBodyJson)
@@ -198,9 +204,9 @@ class DataRestControllerSpec extends Specification {
 
     def 'Get data node with leaves'() {
         given: 'the service returns data node leaves'
-            def xpath = 'xpath'
-            def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/node"
-            mockCpsDataService.getDataNode(dataspaceName, anchorName, xpath, OMIT_DESCENDANTS) >> dataNodeWithLeavesNoChildren
+            def xpath = 'parent-1'
+            def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/node"
+            mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, OMIT_DESCENDANTS) >> [dataNodeWithLeavesNoChildren]
         when: 'get request is performed through REST API'
             def response =
                 mvc.perform(get(endpoint).param('xpath', xpath))
@@ -208,7 +214,7 @@ class DataRestControllerSpec extends Specification {
         then: 'a success response is returned'
             response.status == HttpStatus.OK.value()
         then: 'the response contains the the datanode in json format'
-            response.getContentAsString() == '{"xpath":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}}'
+            response.getContentAsString() == '{"parent-1":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}}'
         and: 'response contains expected leaf and value'
             response.contentAsString.contains('"leaf":"value"')
         and: 'response contains expected leaf-list and values'
@@ -218,8 +224,8 @@ class DataRestControllerSpec extends Specification {
     def 'Get data node with #scenario.'() {
         given: 'the service returns data node with #scenario'
             def xpath = 'some xPath'
-            def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/node"
-            mockCpsDataService.getDataNode(dataspaceName, anchorName, xpath, expectedCpsDataServiceOption) >> dataNode
+            def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/node"
+            mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, expectedCpsDataServiceOption) >> [dataNode]
         when: 'get request is performed through REST API'
             def response =
                 mvc.perform(
@@ -235,25 +241,68 @@ class DataRestControllerSpec extends Specification {
             response.contentAsString.contains('"child"') == expectChildInResponse
         where:
             scenario                    | dataNode                     | includeDescendantsOption || expectedCpsDataServiceOption | expectChildInResponse | expectedRootidentifier
-            'no descendants by default' | dataNodeWithLeavesNoChildren | ''                       || OMIT_DESCENDANTS             | false                 | 'xpath'
-            'no descendant explicitly'  | dataNodeWithLeavesNoChildren | 'false'                  || OMIT_DESCENDANTS             | false                 | 'xpath'
+            'no descendants by default' | dataNodeWithLeavesNoChildren | ''                       || OMIT_DESCENDANTS             | false                 | 'parent-1'
+            'no descendant explicitly'  | dataNodeWithLeavesNoChildren | 'false'                  || OMIT_DESCENDANTS             | false                 | 'parent-1'
             'with descendants'          | dataNodeWithChild            | 'true'                   || INCLUDE_ALL_DESCENDANTS      | true                  | 'parent'
     }
 
+    def 'Get all the data trees as json array with root node xPath using V2'() {
+        given: 'the service returns all data node leaves'
+            def xpath = '/'
+            def endpoint = "$dataNodeBaseEndpointV2/anchors/$anchorName/node"
+            mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, OMIT_DESCENDANTS) >> [dataNodeWithLeavesNoChildren, dataNodeWithLeavesNoChildren2]
+        when: 'V2 of get request is performed through REST API'
+            def response =
+                mvc.perform(get(endpoint).param('xpath', xpath))
+                    .andReturn().response
+        then: 'a success response is returned'
+            response.status == HttpStatus.OK.value()
+        and: 'the response contains the datanode in json array format'
+            response.getContentAsString() == '[{"parent-1":{"leaf":"value","leafList":["leaveListElement1","leaveListElement2"]}},' +
+                '{"parent-2":{"leaf":"value"}}]'
+        and: 'the json array contains expected number of data trees'
+            def numberOfDataTrees = new JsonSlurper().parseText(response.getContentAsString()).iterator().size()
+            assert numberOfDataTrees == 2
+    }
+
+    def 'Get data node with #scenario using V2.'() {
+        given: 'the service returns data nodes with #scenario'
+            def xpath = 'some xPath'
+            def endpoint = "$dataNodeBaseEndpointV2/anchors/$anchorName/node"
+            mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, expectedCpsDataServiceOption) >> [dataNode]
+        when: 'V2 of get request is performed through REST API'
+            def response =
+                mvc.perform(
+                    get(endpoint)
+                        .param('xpath', xpath)
+                        .param('descendants', includeDescendantsOption))
+                    .andReturn().response
+        then: 'a success response is returned'
+            response.status == HttpStatus.OK.value()
+        and: 'the response contains the root node identifier: #expectedRootidentifier'
+            response.contentAsString.contains(expectedRootidentifier)
+        and: 'the response contains child is #expectChildInResponse'
+            response.contentAsString.contains('"child"') == expectChildInResponse
+        where:
+            scenario                    | dataNode                     | includeDescendantsOption || expectedCpsDataServiceOption | expectChildInResponse | expectedRootidentifier
+            'no descendants by default' | dataNodeWithLeavesNoChildren | ''                       || OMIT_DESCENDANTS             | false                 | 'parent-1'
+            'no descendant explicitly'  | dataNodeWithLeavesNoChildren | '0'                      || OMIT_DESCENDANTS             | false                 | 'parent-1'
+            'with descendants'          | dataNodeWithChild            | '-1'                     || INCLUDE_ALL_DESCENDANTS      | true                  | 'parent'
+    }
 
     def 'Get data node using v2 api'() {
         given: 'the service returns data node'
             def xpath = 'some xPath'
             def endpoint = "$dataNodeBaseEndpointV2/anchors/$anchorName/node"
-            mockCpsDataService.getDataNode(dataspaceName, anchorName, xpath, {descendantsOption -> {
-                assert descendantsOption.depth == 2}}) >> dataNodeWithChild
+            mockCpsDataService.getDataNodes(dataspaceName, anchorName, xpath, { descendantsOption -> {
+                assert descendantsOption.depth == 2}} as FetchDescendantsOption) >> [dataNodeWithChild]
         when: 'get request is performed through REST API'
             def response =
                 mvc.perform(
-                        get(endpoint)
-                                .param('xpath', xpath)
-                                .param('descendants', '2'))
-                        .andReturn().response
+                    get(endpoint)
+                        .param('xpath', xpath)
+                        .param('descendants', '2'))
+                    .andReturn().response
         then: 'a success response is returned'
             assert response.status == HttpStatus.OK.value()
         and: 'the response contains the root node identifier'
@@ -264,7 +313,7 @@ class DataRestControllerSpec extends Specification {
 
     def 'Update data node leaves: #scenario.'() {
         given: 'endpoint to update a node '
-            def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
+            def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
         when: 'patch request is performed'
             def response =
                 mvc.perform(
@@ -286,7 +335,7 @@ class DataRestControllerSpec extends Specification {
 
     def 'Update data node leaves with observedTimestamp'() {
         given: 'endpoint to update a node leaves '
-            def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
+            def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
         when: 'patch request is performed'
             def response =
                 mvc.perform(
@@ -309,7 +358,7 @@ class DataRestControllerSpec extends Specification {
 
     def 'Replace data node tree: #scenario.'() {
         given: 'endpoint to replace node'
-            def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
+            def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
         when: 'put request is performed'
             def response =
                 mvc.perform(
@@ -331,7 +380,7 @@ class DataRestControllerSpec extends Specification {
 
     def 'Update data node and descendants with observedTimestamp.'() {
         given: 'endpoint to replace node'
-            def endpoint = "$dataNodeBaseEndpoint/anchors/$anchorName/nodes"
+            def endpoint = "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes"
         when: 'put request is performed'
             def response =
                 mvc.perform(
@@ -354,7 +403,7 @@ class DataRestControllerSpec extends Specification {
 
     def 'Replace list content #scenario.'() {
         when: 'list-nodes endpoint is invoked with put (update) operation'
-            def putRequestBuilder = put("$dataNodeBaseEndpoint/anchors/$anchorName/list-nodes")
+            def putRequestBuilder = put("$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes")
                 .contentType(MediaType.APPLICATION_JSON)
                 .param('xpath', 'parent xpath')
                 .content(requestBodyJson)
@@ -375,7 +424,7 @@ class DataRestControllerSpec extends Specification {
 
     def 'Delete list element #scenario.'() {
         when: 'list-nodes endpoint is invoked with delete operation'
-            def deleteRequestBuilder = delete("$dataNodeBaseEndpoint/anchors/$anchorName/list-nodes")
+            def deleteRequestBuilder = delete("$dataNodeBaseEndpointV1/anchors/$anchorName/list-nodes")
                 .param('xpath', 'list element xpath')
             if (observedTimestamp != null)
                 deleteRequestBuilder.param('observed-timestamp', observedTimestamp)
@@ -396,7 +445,7 @@ class DataRestControllerSpec extends Specification {
         given: 'data node xpath'
             def dataNodeXpath = '/dataNodeXpath'
         when: 'delete data node endpoint is invoked'
-            def deleteDataNodeRequest = delete( "$dataNodeBaseEndpoint/anchors/$anchorName/nodes")
+            def deleteDataNodeRequest = delete( "$dataNodeBaseEndpointV1/anchors/$anchorName/nodes")
                 .param('xpath', dataNodeXpath)
         and: 'observed timestamp is added to the parameters'
             if (observedTimestamp != null)
index 67ee50e..572db00 100644 (file)
@@ -63,7 +63,7 @@ class MultipartFileUtilSpec extends Specification {
 
     def 'Yang file limits in zip archive: #scenario for the bug reported in CPS-1477'() {
         given: 'a yang file size (uncompressed) limit of #threshold bytes'
-            ZipFileSizeValidator.THRESHOLD_SIZE = threshold
+            ZipFileSizeValidator.thresholdSize = threshold
         and: 'an archive with a yang file of 1083 bytes'
             def multipartFile = multipartZipFileFromResource('/yang-files-set-total-1083-bytes.zip')
         when: 'attempt to extract yang files'
index 60ecb2e..b4a6a6a 100644 (file)
@@ -25,7 +25,7 @@ import spock.lang.Specification
 
 class ZipFileSizeValidatorSpec extends Specification {
 
-    def static thresholdSize = ZipFileSizeValidator.THRESHOLD_SIZE
+    def static thresholdSize = ZipFileSizeValidator.thresholdSize
     def static thresholdEntries = ZipFileSizeValidator.THRESHOLD_ENTRIES
     def static thresholdRatio = ZipFileSizeValidator.THRESHOLD_RATIO
 
index ee1d5ac..491acab 100644 (file)
@@ -26,7 +26,7 @@
     <parent>\r
         <groupId>org.onap.cps</groupId>\r
         <artifactId>cps-parent</artifactId>\r
-        <version>3.2.3-SNAPSHOT</version>\r
+        <version>3.2.4-SNAPSHOT</version>\r
         <relativePath>../cps-parent/pom.xml</relativePath>\r
     </parent>\r
 \r
index 2ffbb4a..82afc5a 100755 (executable)
@@ -44,6 +44,7 @@ import lombok.EqualsAndHashCode;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.Setter;
+import lombok.ToString;
 import org.hibernate.annotations.Type;
 import org.hibernate.annotations.TypeDef;
 
@@ -89,6 +90,7 @@ public class FragmentEntity implements Serializable {
     @JoinColumn(name = "anchor_id")
     private AnchorEntity anchor;
 
+    @ToString.Exclude
     @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
     @JoinColumn(name = "parent_id")
     private Set<FragmentEntity> childFragments;
index 2cebfc7..162b268 100755 (executable)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- * Copyright (C) 2020-2022 Nordix Foundation.
+ * Copyright (C) 2020-2023 Nordix Foundation.
  * Modifications Copyright (C) 2020-2022 Bell Canada.
  * Modifications Copyright (C) 2021 Pantheon.tech
  * Modifications Copyright (C) 2022 TechMahindra Ltd.
@@ -130,6 +130,13 @@ public class CpsAdminPersistenceServiceImpl implements CpsAdminPersistenceServic
             .collect(Collectors.toSet());
     }
 
+    @Override
+    public Collection<Anchor> getAnchors(final String dataspaceName, final Collection<String> schemaSetNames) {
+        final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
+        return anchorRepository.findAllByDataspaceAndSchemaSetNameIn(dataspaceEntity, schemaSetNames)
+            .stream().map(CpsAdminPersistenceServiceImpl::toAnchor).collect(Collectors.toSet());
+    }
+
     @Override
     public Collection<Anchor> queryAnchors(final String dataspaceName, final Collection<String> inputModuleNames) {
         try {
@@ -157,6 +164,13 @@ public class CpsAdminPersistenceServiceImpl implements CpsAdminPersistenceServic
         anchorRepository.delete(anchorEntity);
     }
 
+    @Transactional
+    @Override
+    public void deleteAnchors(final String dataspaceName, final Collection<String> anchorNames) {
+        final var dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
+        anchorRepository.deleteAllByDataspaceAndNameIn(dataspaceEntity, anchorNames);
+    }
+
     private AnchorEntity getAnchorEntity(final String dataspaceName, final String anchorName) {
         final var dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
         return anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
index 5b310ef..f634008 100644 (file)
@@ -3,7 +3,7 @@
  *  Copyright (C) 2021-2023 Nordix Foundation
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2020-2022 Bell Canada.
- *  Modifications Copyright (C) 2022 TechMahindra Ltd.
+ *  Modifications Copyright (C) 2022-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.
@@ -57,6 +57,7 @@ import org.onap.cps.spi.exceptions.ConcurrencyException;
 import org.onap.cps.spi.exceptions.CpsAdminException;
 import org.onap.cps.spi.exceptions.CpsPathException;
 import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
+import org.onap.cps.spi.exceptions.DataNodeNotFoundExceptionBatch;
 import org.onap.cps.spi.model.DataNode;
 import org.onap.cps.spi.model.DataNodeBuilder;
 import org.onap.cps.spi.repository.AnchorRepository;
@@ -120,7 +121,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
     private void addNewChildDataNode(final String dataspaceName, final String anchorName,
                                      final String parentNodeXpath, final DataNode newChild) {
         final FragmentEntity parentFragmentEntity =
-            getFragmentWithoutDescendantsByXpath(dataspaceName, anchorName, parentNodeXpath);
+            getFragmentEntity(dataspaceName, anchorName, parentNodeXpath);
         final FragmentEntity newChildAsFragmentEntity =
                 convertToFragmentWithAllDescendants(parentFragmentEntity.getDataspace(),
                         parentFragmentEntity.getAnchor(), newChild);
@@ -136,7 +137,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
     private void addChildrenDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath,
                                       final Collection<DataNode> newChildren) {
         final FragmentEntity parentFragmentEntity =
-            getFragmentWithoutDescendantsByXpath(dataspaceName, anchorName, parentNodeXpath);
+            getFragmentEntity(dataspaceName, anchorName, parentNodeXpath);
         final List<FragmentEntity> fragmentEntities = new ArrayList<>(newChildren.size());
         try {
             newChildren.forEach(newChildAsDataNode -> {
@@ -249,17 +250,28 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
     }
 
     @Override
-    public DataNode getDataNode(final String dataspaceName, final String anchorName, final String xpath,
-                                final FetchDescendantsOption fetchDescendantsOption) {
-        final FragmentEntity fragmentEntity = getFragmentByXpath(dataspaceName, anchorName, xpath,
-            fetchDescendantsOption);
-        return toDataNode(fragmentEntity, fetchDescendantsOption);
+    public Collection<DataNode> getDataNodes(final String dataspaceName, final String anchorName,
+                                             final String xpath,
+                                             final FetchDescendantsOption fetchDescendantsOption) {
+        final String targetXpath = isRootXpath(xpath) ? xpath : CpsPathUtil.getNormalizedXpath(xpath);
+        final Collection<DataNode> dataNodes = getDataNodesForMultipleXpaths(dataspaceName, anchorName,
+                Collections.singletonList(targetXpath), fetchDescendantsOption);
+        if (dataNodes.isEmpty()) {
+            throw new DataNodeNotFoundException(dataspaceName, anchorName, xpath);
+        }
+        return dataNodes;
     }
 
     @Override
-    public Collection<DataNode> getDataNodes(final String dataspaceName, final String anchorName,
-                                             final Collection<String> xpaths,
-                                             final FetchDescendantsOption fetchDescendantsOption) {
+    public Collection<DataNode> getDataNodesForMultipleXpaths(final String dataspaceName, final String anchorName,
+                                                              final Collection<String> xpaths,
+                                                              final FetchDescendantsOption fetchDescendantsOption) {
+        final Collection<FragmentEntity> fragmentEntities = getFragmentEntities(dataspaceName, anchorName, xpaths);
+        return toDataNodes(fragmentEntities, fetchDescendantsOption);
+    }
+
+    private Collection<FragmentEntity> getFragmentEntities(final String dataspaceName, final String anchorName,
+                                                           final Collection<String> xpaths) {
         final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
         final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
 
@@ -271,7 +283,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
             try {
                 normalizedXpaths.add(CpsPathUtil.getNormalizedXpath(xpath));
             } catch (final PathParsingException e) {
-                log.warn("Error parsing xpath \"{}\" in getDataNodes: {}", xpath, e.getMessage());
+                log.warn("Error parsing xpath \"{}\": {}", xpath, e.getMessage());
             }
         }
         final Collection<FragmentEntity> fragmentEntities =
@@ -283,17 +295,10 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
             fragmentEntities.addAll(FragmentEntityArranger.toFragmentEntityTrees(anchorEntity, fragmentExtracts));
         }
 
-        return toDataNodes(fragmentEntities, fetchDescendantsOption);
+        return fragmentEntities;
     }
 
-    private FragmentEntity getFragmentWithoutDescendantsByXpath(final String dataspaceName,
-                                                                final String anchorName,
-                                                                final String xpath) {
-        return getFragmentByXpath(dataspaceName, anchorName, xpath, FetchDescendantsOption.OMIT_DESCENDANTS);
-    }
-
-    private FragmentEntity getFragmentByXpath(final String dataspaceName, final String anchorName,
-                                              final String xpath, final FetchDescendantsOption fetchDescendantsOption) {
+    private FragmentEntity getFragmentEntity(final String dataspaceName, final String anchorName, final String xpath) {
         final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
         final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
         final FragmentEntity fragmentEntity;
@@ -304,13 +309,8 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
                 .stream().findFirst().orElse(null);
         } else {
             final String normalizedXpath = getNormalizedXpath(xpath);
-            if (FetchDescendantsOption.OMIT_DESCENDANTS.equals(fetchDescendantsOption)) {
-                fragmentEntity =
-                    fragmentRepository.getByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity, normalizedXpath);
-            } else {
-                fragmentEntity = buildFragmentEntitiesFromFragmentExtracts(anchorEntity, normalizedXpath)
-                    .stream().findFirst().orElse(null);
-            }
+            fragmentEntity =
+                fragmentRepository.getByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity, normalizedXpath);
         }
         if (fragmentEntity == null) {
             throw new DataNodeNotFoundException(dataspaceEntity.getName(), anchorEntity.getName(), xpath);
@@ -486,7 +486,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
     @Override
     public void updateDataLeaves(final String dataspaceName, final String anchorName, final String xpath,
                                  final Map<String, Serializable> updateLeaves) {
-        final FragmentEntity fragmentEntity = getFragmentWithoutDescendantsByXpath(dataspaceName, anchorName, xpath);
+        final FragmentEntity fragmentEntity = getFragmentEntity(dataspaceName, anchorName, xpath);
         final String currentLeavesAsString = fragmentEntity.getAttributes();
         final String mergedLeaves = mergeLeaves(updateLeaves, currentLeavesAsString);
         fragmentEntity.setAttributes(mergedLeaves);
@@ -496,8 +496,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
     @Override
     public void updateDataNodeAndDescendants(final String dataspaceName, final String anchorName,
                                              final DataNode dataNode) {
-        final FragmentEntity fragmentEntity =
-            getFragmentWithoutDescendantsByXpath(dataspaceName, anchorName, dataNode.getXpath());
+        final FragmentEntity fragmentEntity = getFragmentEntity(dataspaceName, anchorName, dataNode.getXpath());
         updateFragmentEntityAndDescendantsWithDataNode(fragmentEntity, dataNode);
         try {
             fragmentRepository.save(fragmentEntity);
@@ -509,21 +508,24 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
     }
 
     @Override
-    public void updateDataNodesAndDescendants(final String dataspaceName,
-                                              final String anchorName,
-                                              final List<DataNode> dataNodes) {
-
-        final Map<DataNode, FragmentEntity> dataNodeFragmentEntityMap = dataNodes.stream()
-                .collect(Collectors.toMap(
-                        dataNode -> dataNode,
-                        dataNode ->
-                            getFragmentWithoutDescendantsByXpath(dataspaceName, anchorName, dataNode.getXpath())));
-        dataNodeFragmentEntityMap.forEach(
-                (dataNode, fragmentEntity) -> updateFragmentEntityAndDescendantsWithDataNode(fragmentEntity, dataNode));
+    public void updateDataNodesAndDescendants(final String dataspaceName, final String anchorName,
+                                              final List<DataNode> updatedDataNodes) {
+        final Map<String, DataNode> xpathToUpdatedDataNode = updatedDataNodes.stream()
+            .collect(Collectors.toMap(DataNode::getXpath, dataNode -> dataNode));
+
+        final Collection<String> xpaths = xpathToUpdatedDataNode.keySet();
+        final Collection<FragmentEntity> existingFragmentEntities =
+            getFragmentEntities(dataspaceName, anchorName, xpaths);
+
+        for (final FragmentEntity existingFragmentEntity : existingFragmentEntities) {
+            final DataNode updatedDataNode = xpathToUpdatedDataNode.get(existingFragmentEntity.getXpath());
+            updateFragmentEntityAndDescendantsWithDataNode(existingFragmentEntity, updatedDataNode);
+        }
+
         try {
-            fragmentRepository.saveAll(dataNodeFragmentEntityMap.values());
+            fragmentRepository.saveAll(existingFragmentEntities);
         } catch (final StaleStateException staleStateException) {
-            retryUpdateDataNodesIndividually(dataspaceName, anchorName, dataNodeFragmentEntityMap.values());
+            retryUpdateDataNodesIndividually(dataspaceName, anchorName, existingFragmentEntities);
         }
     }
 
@@ -577,8 +579,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
     @Transactional
     public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath,
                                    final Collection<DataNode> newListElements) {
-        final FragmentEntity parentEntity =
-            getFragmentWithoutDescendantsByXpath(dataspaceName, anchorName, parentNodeXpath);
+        final FragmentEntity parentEntity = getFragmentEntity(dataspaceName, anchorName, parentNodeXpath);
         final String listElementXpathPrefix = getListElementXpathPrefix(newListElements);
         final Map<String, FragmentEntity> existingListElementFragmentEntitiesByXPath =
                 extractListElementFragmentEntitiesByXPath(parentEntity.getChildFragments(), listElementXpathPrefix);
@@ -605,6 +606,15 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
                         anchorEntity -> fragmentRepository.deleteByAnchorIn(Set.of(anchorEntity)));
     }
 
+    @Override
+    @Transactional
+    public void deleteDataNodes(final String dataspaceName, final Collection<String> anchorNames) {
+        final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
+        final Collection<AnchorEntity> anchorEntities =
+            anchorRepository.findAllByDataspaceAndNameIn(dataspaceEntity, anchorNames);
+        fragmentRepository.deleteByAnchorIn(anchorEntities);
+    }
+
     @Override
     @Transactional
     public void deleteDataNodes(final String dataspaceName, final String anchorName,
@@ -612,17 +622,30 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
         final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
         final AnchorEntity anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, anchorName);
 
-        final Collection<String> normalizedXpaths = new ArrayList<>(xpathsToDelete.size());
+        final Collection<String> deleteChecklist = new HashSet<>(xpathsToDelete.size());
         for (final String xpath : xpathsToDelete) {
             try {
-                normalizedXpaths.add(CpsPathUtil.getNormalizedXpath(xpath));
+                deleteChecklist.add(CpsPathUtil.getNormalizedXpath(xpath));
             } catch (final PathParsingException e) {
-                log.debug("Error parsing xpath \"{}\" in deleteDataNodes: {}", xpath, e.getMessage());
+                log.debug("Error parsing xpath \"{}\": {}", xpath, e.getMessage());
             }
         }
 
-        fragmentRepository.deleteByAnchorIdAndXpaths(anchorEntity.getId(), normalizedXpaths);
-        fragmentRepository.deleteListsByAnchorIdAndXpaths(anchorEntity.getId(), normalizedXpaths);
+        final Collection<String> xpathsToExistingContainers =
+            fragmentRepository.findAllXpathByAnchorAndXpathIn(anchorEntity, deleteChecklist);
+        deleteChecklist.removeAll(xpathsToExistingContainers);
+
+        final Collection<String> xpathsToExistingLists = deleteChecklist.stream()
+            .filter(xpath -> fragmentRepository.existsByAnchorAndXpathStartsWith(anchorEntity, xpath + "["))
+            .collect(Collectors.toList());
+        deleteChecklist.removeAll(xpathsToExistingLists);
+
+        if (!deleteChecklist.isEmpty()) {
+            throw new DataNodeNotFoundExceptionBatch(dataspaceName, anchorName, deleteChecklist);
+        }
+
+        fragmentRepository.deleteByAnchorIdAndXpaths(anchorEntity.getId(), xpathsToExistingContainers);
+        fragmentRepository.deleteListsByAnchorIdAndXpaths(anchorEntity.getId(), xpathsToExistingLists);
     }
 
     @Override
@@ -652,7 +675,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
             } else {
                 parentNodeXpath = CpsPathUtil.getNormalizedParentXpath(targetXpath);
             }
-            parentFragmentEntity = getFragmentWithoutDescendantsByXpath(dataspaceName, anchorName, parentNodeXpath);
+            parentFragmentEntity = getFragmentEntity(dataspaceName, anchorName, parentNodeXpath);
             if (CpsPathUtil.isPathToListElement(targetXpath)) {
                 targetDeleted = deleteDataNode(parentFragmentEntity, targetXpath);
             } else {
index 3dbd578..46b0fec 100755 (executable)
@@ -47,6 +47,12 @@ public interface AnchorRepository extends JpaRepository<AnchorEntity, Integer> {
 
     Collection<AnchorEntity> findAllBySchemaSet(@NotNull SchemaSetEntity schemaSetEntity);
 
+    Collection<AnchorEntity> findAllByDataspaceAndNameIn(@NotNull DataspaceEntity dataspaceEntity,
+                                                         @NotNull Collection<String> anchorNames);
+
+    Collection<AnchorEntity> findAllByDataspaceAndSchemaSetNameIn(@NotNull DataspaceEntity dataspaceEntity,
+                                                                  @NotNull Collection<String> schemaSetNames);
+
     Integer countByDataspace(@NotNull DataspaceEntity dataspaceEntity);
 
     @Query(value = "SELECT anchor.* FROM yang_resource\n"
@@ -58,4 +64,7 @@ public interface AnchorRepository extends JpaRepository<AnchorEntity, Integer> {
         + "HAVING COUNT(DISTINCT module_name) = :sizeOfModuleNames", nativeQuery = true)
     Collection<AnchorEntity> getAnchorsByDataspaceIdAndModuleNames(@Param("dataspaceId") int dataspaceId,
         @Param("moduleNames") Collection<String> moduleNames, @Param("sizeOfModuleNames") int sizeOfModuleNames);
-}
\ No newline at end of file
+
+    void deleteAllByDataspaceAndNameIn(@NotNull DataspaceEntity dataspaceEntity,
+                                       @NotNull Collection<String> anchorNames);
+}
index 0e4d359..5c5458a 100644 (file)
 package org.onap.cps.spi.repository;
 
 import java.util.Collection;
+import java.util.Collections;
+import java.util.stream.Collectors;
 import javax.persistence.EntityManager;
 import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
 import lombok.RequiredArgsConstructor;
 
 @RequiredArgsConstructor
@@ -40,55 +43,55 @@ public class FragmentNativeRepositoryImpl implements FragmentNativeRepository {
     @PersistenceContext
     private final EntityManager entityManager;
 
-    private final TempTableCreator tempTableCreator;
-
     @Override
     public void deleteFragmentEntity(final long fragmentEntityId) {
         entityManager.createNativeQuery(
-                DROP_FRAGMENT_CONSTRAINT
-                    + ADD_FRAGMENT_CONSTRAINT_WITH_CASCADE
-                    + "DELETE FROM fragment WHERE id = ?;"
-                    + DROP_FRAGMENT_CONSTRAINT
-                    + ADD_ORIGINAL_FRAGMENT_CONSTRAINT)
+                addFragmentConstraintWithDeleteCascade("DELETE FROM fragment WHERE id = ?"))
             .setParameter(1, fragmentEntityId)
             .executeUpdate();
     }
 
     @Override
-    // Accept security hotspot as temporary table name in SQL query is created internally, not from user input.
-    @SuppressWarnings("squid:S2077")
     public void deleteByAnchorIdAndXpaths(final int anchorId, final Collection<String> xpaths) {
-        if (!xpaths.isEmpty()) {
-            final String tempTableName = tempTableCreator.createTemporaryTable("xpathsToDelete", xpaths, "xpath");
-            entityManager.createNativeQuery(
-                    DROP_FRAGMENT_CONSTRAINT
-                        + ADD_FRAGMENT_CONSTRAINT_WITH_CASCADE
-                        + "DELETE FROM fragment f USING " + tempTableName + " t"
-                        + " WHERE f.anchor_id = :anchorId AND f.xpath = t.xpath;"
-                        + DROP_FRAGMENT_CONSTRAINT
-                        + ADD_ORIGINAL_FRAGMENT_CONSTRAINT)
-                .setParameter("anchorId", anchorId)
-                .executeUpdate();
-        }
+        final String queryString = addFragmentConstraintWithDeleteCascade(
+            "DELETE FROM fragment f WHERE f.anchor_id = ? AND (f.xpath IN (:parameterPlaceholders))");
+        executeUpdateWithAnchorIdAndCollection(queryString, anchorId, xpaths);
     }
 
     @Override
-    // Accept security hotspot as temporary table name in SQL query is created internally, not from user input.
+    public void deleteListsByAnchorIdAndXpaths(final int anchorId, final Collection<String> listXpaths) {
+        final Collection<String> listXpathPatterns =
+            listXpaths.stream().map(listXpath -> listXpath + "[%").collect(Collectors.toSet());
+        final String queryString = addFragmentConstraintWithDeleteCascade(
+            "DELETE FROM fragment f WHERE f.anchor_id = ? AND (f.xpath LIKE ANY (array[:parameterPlaceholders]))");
+        executeUpdateWithAnchorIdAndCollection(queryString, anchorId, listXpathPatterns);
+    }
+
+    // Accept security hotspot as placeholders in SQL query are created internally, not from user input.
     @SuppressWarnings("squid:S2077")
-    public void deleteListsByAnchorIdAndXpaths(final int anchorId, final Collection<String> xpaths) {
-        if (!xpaths.isEmpty()) {
-            final String tempTableName = tempTableCreator.createTemporaryTable("xpathsToDelete", xpaths, "xpath");
-            entityManager.createNativeQuery(
-                DROP_FRAGMENT_CONSTRAINT
-                    + ADD_FRAGMENT_CONSTRAINT_WITH_CASCADE
-                    + "DELETE FROM fragment f USING " + tempTableName + " t"
-                    + " WHERE f.anchor_id = :anchorId AND f.xpath LIKE CONCAT(t.xpath, :xpathListPattern);"
-                    + DROP_FRAGMENT_CONSTRAINT
-                    + ADD_ORIGINAL_FRAGMENT_CONSTRAINT)
-                .setParameter("anchorId", anchorId)
-                .setParameter("xpathListPattern", "[%%")
-                .executeUpdate();
+    private void executeUpdateWithAnchorIdAndCollection(final String sqlTemplate, final int anchorId,
+                                                        final Collection<String> collection) {
+        if (!collection.isEmpty()) {
+            final String parameterPlaceholders = String.join(",", Collections.nCopies(collection.size(), "?"));
+            final String queryStringWithParameterPlaceholders =
+                sqlTemplate.replaceFirst(":parameterPlaceholders\\b", parameterPlaceholders);
+
+            final Query query = entityManager.createNativeQuery(queryStringWithParameterPlaceholders);
+            query.setParameter(1, anchorId);
+            int parameterIndex = 2;
+            for (final String parameterValue : collection) {
+                query.setParameter(parameterIndex++, parameterValue);
+            }
+            query.executeUpdate();
         }
     }
 
+    private static String addFragmentConstraintWithDeleteCascade(final String queryString) {
+        return DROP_FRAGMENT_CONSTRAINT
+            + ADD_FRAGMENT_CONSTRAINT_WITH_CASCADE
+            + queryString + ";"
+            + DROP_FRAGMENT_CONSTRAINT
+            + ADD_ORIGINAL_FRAGMENT_CONSTRAINT;
+    }
+
 }
index 8bdb7d9..51ebcb4 100755 (executable)
@@ -100,4 +100,10 @@ public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>,
         nativeQuery = true)\r
     List<FragmentExtract> quickFindWithDescendants(@Param("anchorId") int anchorId,\r
                                                    @Param("xpathRegex") String xpathRegex);\r
+\r
+    @Query("SELECT f.xpath FROM FragmentEntity f WHERE f.anchor = :anchor AND f.xpath IN :xpaths")\r
+    List<String> findAllXpathByAnchorAndXpathIn(@Param("anchor") AnchorEntity anchorEntity,\r
+                                                @Param("xpaths") Collection<String> xpaths);\r
+\r
+    boolean existsByAnchorAndXpathStartsWith(AnchorEntity anchorEntity, String xpath);\r
 }\r
index 338b0b1..d798932 100644 (file)
@@ -60,6 +60,7 @@ public class TempTableCreator {
         final StringBuilder sqlStringBuilder = new StringBuilder("CREATE TEMPORARY TABLE ");
         sqlStringBuilder.append(tempTableName);
         defineColumns(sqlStringBuilder, columnNames);
+        sqlStringBuilder.append(" ON COMMIT DROP;");
         insertData(sqlStringBuilder, tempTableName, columnNames, sqlData);
         entityManager.createNativeQuery(sqlStringBuilder.toString()).executeUpdate();
         return tempTableName;
@@ -95,7 +96,7 @@ public class TempTableCreator {
                 sqlStringBuilder.append(",");
             }
         }
-        sqlStringBuilder.append(");");
+        sqlStringBuilder.append(")");
     }
 
     private static void insertData(final StringBuilder sqlStringBuilder,
index 99d44aa..28d3bcf 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2022 Nordix Foundation
+ *  Copyright (C) 2021-2023 Nordix Foundation
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2022 Bell Canada
  *  Modifications Copyright (C) 2022 TechMahindra Ltd.
@@ -142,7 +142,8 @@ class CpsAdminPersistenceServiceSpec extends CpsPersistenceSpecBase {
         where: 'the following data is used'
             dataspaceName          || expectedAnchors
             DATASPACE_NAME         || [Anchor.builder().name(ANCHOR_NAME1).schemaSetName(SCHEMA_SET_NAME1).dataspaceName(DATASPACE_NAME).build(),
-                                       Anchor.builder().name(ANCHOR_NAME2).schemaSetName(SCHEMA_SET_NAME2).dataspaceName(DATASPACE_NAME).build()]
+                                       Anchor.builder().name(ANCHOR_NAME2).schemaSetName(SCHEMA_SET_NAME2).dataspaceName(DATASPACE_NAME).build(),
+                                       Anchor.builder().name(ANCHOR_NAME3).schemaSetName(SCHEMA_SET_NAME2).dataspaceName(DATASPACE_NAME).build()]
             DATASPACE_WITH_NO_DATA || []
     }
 
@@ -178,6 +179,17 @@ class CpsAdminPersistenceServiceSpec extends CpsPersistenceSpecBase {
             thrown(DataspaceNotFoundException)
     }
 
+    @Sql([CLEAR_DATA, SET_DATA])
+    def 'Get all anchors associated with multiple schemasets in a dataspace.'() {
+        when: 'anchors are retrieved by dataspace and schema-sets'
+            def anchors = objectUnderTest.getAnchors('DATASPACE-001', ['SCHEMA-SET-001', 'SCHEMA-SET-002'])
+        then: ' the response contains expected anchors'
+            anchors == Set.of(
+                new Anchor('ANCHOR-001', 'DATASPACE-001', 'SCHEMA-SET-001'),
+                new Anchor('ANCHOR-002', 'DATASPACE-001', 'SCHEMA-SET-002'),
+                new Anchor('ANCHOR-003', 'DATASPACE-001', 'SCHEMA-SET-002'))
+    }
+
     @Sql([CLEAR_DATA, SET_DATA])
     def 'Delete anchor'() {
         when: 'delete anchor action is invoked'
@@ -198,6 +210,15 @@ class CpsAdminPersistenceServiceSpec extends CpsPersistenceSpecBase {
             'anchor does not exists'   | DATASPACE_NAME | 'unknown'      || AnchorNotFoundException
     }
 
+    @Sql([CLEAR_DATA, SET_DATA])
+    def 'Delete multiple anchors'() {
+        when: 'delete anchors action is invoked'
+            objectUnderTest.deleteAnchors(DATASPACE_NAME, ['ANCHOR-002', 'ANCHOR-003'])
+        then: 'anchors are deleted'
+            anchorRepository.findById(3002).isEmpty()
+            anchorRepository.findById(3003).isEmpty()
+    }
+
     @Sql([CLEAR_DATA, SAMPLE_DATA_FOR_ANCHORS_WITH_MODULES])
     def 'Query anchors that have #scenario.'() {
         when: 'all anchor are retrieved for the given dataspace name and module names'
@@ -236,7 +257,7 @@ class CpsAdminPersistenceServiceSpec extends CpsPersistenceSpecBase {
         where: 'the following data is used'
             scenario                        | dataspaceName   || expectedException          | expectedMessageDetails
             'dataspace name does not exist' | 'unknown'       || DataspaceNotFoundException | 'unknown does not exist'
-            'dataspace contains an anchor'  | 'DATASPACE-001' || DataspaceInUseException    | 'contains 2 anchor(s)'
+            'dataspace contains an anchor'  | 'DATASPACE-001' || DataspaceInUseException    | 'contains 3 anchor(s)'
             'dataspace contains schemasets' | 'DATASPACE-003' || DataspaceInUseException    | 'contains 1 schemaset(s)'
     }
 }
index e4c5529..28916b1 100755 (executable)
@@ -3,7 +3,7 @@
  *  Copyright (C) 2021-2023 Nordix Foundation
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2021-2022 Bell Canada.
- *  Modifications Copyright (C) 2022 TechMahindra Ltd.
+ *  Modifications Copyright (C) 2022-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.
@@ -54,11 +54,8 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
     static DataNodeBuilder dataNodeBuilder = new DataNodeBuilder()
 
     static final String SET_DATA = '/data/fragment.sql'
-    static int DATASPACE_1001_ID = 1001L
-    static int ANCHOR_3003_ID = 3003L
     static long ID_DATA_NODE_WITH_DESCENDANTS = 4001
     static String XPATH_DATA_NODE_WITH_DESCENDANTS = '/parent-1'
-    static String XPATH_DATA_NODE_WITH_LEAVES = '/parent-207'
     static long DATA_NODE_202_FRAGMENT_ID = 4202L
     static long CHILD_OF_DATA_NODE_202_FRAGMENT_ID = 4203L
     static long LIST_DATA_NODE_PARENT201_FRAGMENT_ID = 4206L
@@ -82,15 +79,13 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
     ]
 
     @Sql([CLEAR_DATA, SET_DATA])
-    def 'Get existing datanode with descendants.'() {
-        when: 'the node is retrieved by its xpath'
-            def dataNode = objectUnderTest.getDataNode(DATASPACE_NAME, ANCHOR_NAME1, '/parent-1', INCLUDE_ALL_DESCENDANTS)
-        then: 'the path and prefix are populated correctly'
-            assert dataNode.xpath == '/parent-1'
-        and: 'dataNode has no prefix (to be addressed by CPS-1301'
-            assert dataNode.moduleNamePrefix == null
-        and: 'the child node has the correct path'
-            assert dataNode.childDataNodes[0].xpath == '/parent-1/child-1'
+    def 'Get all datanodes with descendants .'() {
+        when: 'data nodes are retrieved by their xpath'
+            def dataNodes = objectUnderTest.getDataNodesForMultipleXpaths(DATASPACE_NAME, ANCHOR_NAME1, ['/parent-1'], INCLUDE_ALL_DESCENDANTS)
+        then: 'same data nodes are returned by getDataNodesForMultipleXpaths method'
+            assert objectUnderTest.getDataNodes(DATASPACE_NAME, ANCHOR_NAME1, '/parent-1', INCLUDE_ALL_DESCENDANTS) == dataNodes
+        and: 'the dataNodes have no prefix (to be addressed by CPS-1301)'
+            assert dataNodes[0].moduleNamePrefix == null
     }
 
     @Sql([CLEAR_DATA, SET_DATA])
@@ -102,11 +97,11 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
             def dataNodes = [createDataNodeTree(parentXpath, childXpath, grandChildXpath)]
             objectUnderTest.storeDataNodes(DATASPACE_NAME, ANCHOR_NAME1, dataNodes)
         then: 'it can be retrieved by its xpath'
-            def dataNode = objectUnderTest.getDataNode(DATASPACE_NAME, ANCHOR_NAME1, parentXpath, INCLUDE_ALL_DESCENDANTS)
-            assert dataNode.xpath == parentXpath
+            def dataNode = objectUnderTest.getDataNodes(DATASPACE_NAME, ANCHOR_NAME1, parentXpath, INCLUDE_ALL_DESCENDANTS)
+            assert dataNode[0].xpath == parentXpath
         and: 'it has the correct child'
-            assert dataNode.childDataNodes.size() == 1
-            def childDataNode = dataNode.childDataNodes[0]
+            assert dataNode[0].childDataNodes.size() == 1
+            def childDataNode = dataNode[0].childDataNodes[0]
             assert childDataNode.xpath == childXpath
         and: 'and its grandchild'
             assert childDataNode.childDataNodes.size() == 1
@@ -236,18 +231,19 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
     }
 
     @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_HAVING_SINGLE_TOP_LEVEL_FRAGMENT,
-                    inputXPath, OMIT_DESCENDANTS)
-        then: 'data node is returned with no descendants'
-            assert result.xpath == XPATH_DATA_NODE_WITH_LEAVES
-        and: 'expected leaves'
-            assert result.childDataNodes.size() == 0
-            assertLeavesMaps(result.leaves, expectedLeavesByXpathMap[XPATH_DATA_NODE_WITH_LEAVES])
-        where: 'the following data is used'
+    def 'Get all data nodes by single xpath without descendants : #scenario'() {
+        when: 'data nodes are requested'
+            def result = objectUnderTest.getDataNodesForMultipleXpaths(DATASPACE_NAME, ANCHOR_WITH_MULTIPLE_TOP_LEVEL_FRAGMENTS,
+                [inputXPath], OMIT_DESCENDANTS)
+        then: 'data nodes under root are returned'
+            assert result.childDataNodes.size() == 2
+        and: 'no descendants of parent nodes are returned'
+            result.each {assert it.childDataNodes.size() == 0}
+        and: 'same data nodes are returned when V2 of get Data Nodes API is executed'
+            assert objectUnderTest.getDataNodes(DATASPACE_NAME, ANCHOR_WITH_MULTIPLE_TOP_LEVEL_FRAGMENTS,
+                inputXPath, OMIT_DESCENDANTS) == result
+        where: 'the following xpath is used'
             scenario      | inputXPath
-            'some xpath'  | '/parent-207'
             'root xpath'  | '/'
             'empty xpath' | ''
     }
@@ -255,51 +251,50 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
     @Sql([CLEAR_DATA, SET_DATA])
     def 'Cps Path query with syntax error throws a CPS Path Exception.'() {
         when: 'trying to execute a query with a syntax (parsing) error'
-            objectUnderTest.getDataNode(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, 'invalid-cps-path/child' , OMIT_DESCENDANTS)
+            objectUnderTest.getDataNodes(DATASPACE_NAME, ANCHOR_FOR_DATA_NODES_WITH_LEAVES, 'invalid-cps-path/child' , OMIT_DESCENDANTS)
         then: 'exception is thrown'
-            def exceptionThrown = thrown(CpsPathException)
-            assert exceptionThrown.getDetails().contains('failed to parse at line 1 due to extraneous input \'invalid-cps-path\' expecting \'/\'')
+            def exceptionThrown = thrown(PathParsingException)
+            assert exceptionThrown.getMessage().contains('failed to parse at line 1 due to extraneous input \'invalid-cps-path\' expecting \'/\'')
     }
 
     @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_HAVING_SINGLE_TOP_LEVEL_FRAGMENT,
-                    inputXPath, INCLUDE_ALL_DESCENDANTS)
-            def mappedResult = treeToFlatMapByXpath(new HashMap<>(), result)
-        then: 'data node is returned with all the descendants populated'
-            assert mappedResult.size() == 4
+    def 'Get all data nodes by single xpath with all descendants : #scenario'() {
+        when: 'data nodes are requested with all descendants'
+            def result = objectUnderTest.getDataNodesForMultipleXpaths(DATASPACE_NAME, ANCHOR_WITH_MULTIPLE_TOP_LEVEL_FRAGMENTS,
+                [inputXPath], INCLUDE_ALL_DESCENDANTS)
+            def mappedResult = multipleTreesToFlatMapByXpath(new HashMap<>(), result)
+        then: 'data nodes are returned with all the descendants populated'
+            assert mappedResult.size() == 8
             assert result.childDataNodes.size() == 2
-            assert mappedResult.get('/parent-207/child-001').childDataNodes.size() == 0
-            assert mappedResult.get('/parent-207/child-002').childDataNodes.size() == 1
-        and: 'extracted leaves maps are matching expected'
-            mappedResult.forEach(
-                    (xPath, dataNode) -> assertLeavesMaps(dataNode.leaves, expectedLeavesByXpathMap[xPath]))
+            assert mappedResult.get('/parent-208/child-001').childDataNodes.size() == 0
+            assert mappedResult.get('/parent-208/child-002').childDataNodes.size() == 1
+            assert mappedResult.get('/parent-209/child-001').childDataNodes.size() == 0
+            assert mappedResult.get('/parent-209/child-002').childDataNodes.size() == 1
+        and: 'same data nodes are returned when V2 of Get Data Nodes API is executed'
+            assert objectUnderTest.getDataNodes(DATASPACE_NAME, ANCHOR_WITH_MULTIPLE_TOP_LEVEL_FRAGMENTS,
+                inputXPath, INCLUDE_ALL_DESCENDANTS) == result
         where: 'the following data is used'
             scenario      | inputXPath
-            'some xpath'  | '/parent-207'
             'root xpath'  | '/'
             'empty xpath' | ''
     }
 
     @Sql([CLEAR_DATA, SET_DATA])
-    def 'Get data node error scenario: #scenario.'() {
-        when: 'attempt to get data node with #scenario'
-            objectUnderTest.getDataNode(dataspaceName, anchorName, xpath, OMIT_DESCENDANTS)
-        then: 'a #expectedException is thrown'
+    def 'Get data nodes error scenario : #scenario.'() {
+        when: 'attempt to get data nodes with #scenario'
+            objectUnderTest.getDataNodes(dataspaceName, anchorName, xpath, OMIT_DESCENDANTS)
+        then: 'an #expectedException is thrown'
             thrown(expectedException)
         where: 'the following data is used'
-            scenario                 | dataspaceName  | anchorName                        | xpath           || expectedException
-            'non-existing dataspace' | 'NO DATASPACE' | 'not relevant'                    | '/not relevant' || DataspaceNotFoundException
-            'non-existing anchor'    | DATASPACE_NAME | 'NO ANCHOR'                       | '/not relevant' || AnchorNotFoundException
-            'non-existing xpath'     | DATASPACE_NAME | ANCHOR_FOR_DATA_NODES_WITH_LEAVES | '/NO-XPATH'     || DataNodeNotFoundException
-            'invalid xpath'          | DATASPACE_NAME | ANCHOR_FOR_DATA_NODES_WITH_LEAVES | 'INVALID XPATH' || CpsPathException
+            scenario             | dataspaceName  | anchorName                        | xpath           || expectedException
+            'non existing xpath' | DATASPACE_NAME | ANCHOR_FOR_DATA_NODES_WITH_LEAVES | '/NO-XPATH'     || DataNodeNotFoundException
+            'invalid Xpath'      | DATASPACE_NAME | ANCHOR_FOR_DATA_NODES_WITH_LEAVES | 'INVALID XPATH' || PathParsingException
     }
 
     @Sql([CLEAR_DATA, SET_DATA])
-    def 'Get multiple data nodes by xpath.'() {
+    def 'Get data nodes for multiple xpaths.'() {
         when: 'fetch #scenario.'
-            def results = objectUnderTest.getDataNodes(DATASPACE_NAME, ANCHOR_NAME3, inputXpaths, OMIT_DESCENDANTS)
+            def results = objectUnderTest.getDataNodesForMultipleXpaths(DATASPACE_NAME, ANCHOR_NAME3, inputXpaths, OMIT_DESCENDANTS)
         then: 'the expected number of data nodes are returned'
             assert results.size() == expectedResultSize
         where: 'following parameters were used'
@@ -312,6 +307,7 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
             '2 unique nodes with duplicate xpath'  | ["/parent-200", "/parent-202", "/parent-200"]   || 2
             'list element with key (single quote)' | ["/parent-201/child-204[@key='A']"]             || 1
             'list element with key (double quote)' | ['/parent-201/child-204[@key="A"]']             || 1
+            'whole list (not implemented)'         | ["/parent-201/child-204"]                       || 0
             'non-existing xpath'                   | ["/NO-XPATH"]                                   || 0
             'existing and non-existing xpaths'     | ["/parent-200", "/NO-XPATH", "/parent-201"]     || 2
             'invalid xpath'                        | ["INVALID XPATH"]                               || 0
@@ -323,9 +319,9 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
     }
 
     @Sql([CLEAR_DATA, SET_DATA])
-    def 'Get multiple data nodes error scenario: #scenario.'() {
+    def 'Get data nodes for collection of xpath error scenario : #scenario.'() {
         when: 'attempt to get data nodes with #scenario'
-            objectUnderTest.getDataNodes(dataspaceName, anchorName, ['/not-relevant'], OMIT_DESCENDANTS)
+            objectUnderTest.getDataNodesForMultipleXpaths(dataspaceName, anchorName, ['/not-relevant'], OMIT_DESCENDANTS)
         then: 'a #expectedException is thrown'
             thrown(expectedException)
         where: 'the following data is used'
@@ -588,10 +584,22 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
             'whole list'                      | ['/parent-203/child-204']                                              || ['/parent-203/child-203']
             'list and element in same list'   | ['/parent-203/child-204', '/parent-203/child-204[@key="A"]']           || ['/parent-203/child-203']
             'list element under list element' | ['/parent-203/child-204[@key="B"]/grand-child-204[@key2="Y"]']         || ["/parent-203/child-203", "/parent-203/child-204[@key='A']", "/parent-203/child-204[@key='B']"]
-            'valid but non-existing xpath'    | ['/non-existing', '/parent-203/child-204']                             || ['/parent-203/child-203']
             'invalid xpath'                   | ['INVALID XPATH', '/parent-203/child-204']                             || ['/parent-203/child-203']
     }
 
+    @Sql([CLEAR_DATA, SET_DATA])
+    def 'Delete multiple data nodes error scenario: #scenario.'() {
+        when: 'deleting nodes is executed for: #scenario.'
+            objectUnderTest.deleteDataNodes(dataspaceName, anchorName, targetXpaths)
+        then: 'a #expectedException is thrown'
+            thrown(expectedException)
+        where: 'the following data is used'
+            scenario                 | dataspaceName  | anchorName     | targetXpaths            || expectedException
+            'non-existing dataspace' | 'NO DATASPACE' | 'not relevant' | ['/not relevant']       || DataspaceNotFoundException
+            'non-existing anchor'    | DATASPACE_NAME | 'NO ANCHOR'    | ['/not relevant']       || AnchorNotFoundException
+            'non-existing datanode'  | DATASPACE_NAME | ANCHOR_NAME3   | ['/NON-EXISTING-XPATH'] || DataNodeNotFoundException
+    }
+
     @Sql([CLEAR_DATA, SET_DATA])
     def 'Delete data nodes with "/"-token in list key value: #scenario. (CPS-1409)'() {
         given: 'a data nodes with list-element child with "/" in index value (and grandchild)'
@@ -599,11 +607,11 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
             def child = new DataNodeBuilder().withXpath(deleteTestChildXpath).withChildDataNodes([grandChild]).build()
             objectUnderTest.addChildDataNode(DATASPACE_NAME, ANCHOR_NAME3, deleteTestParentXPath, child)
         and: 'number of children before delete is stored'
-            def numberOfChildrenBeforeDelete = objectUnderTest.getDataNode(DATASPACE_NAME, ANCHOR_NAME3, pathToParentOfDeletedNode, INCLUDE_ALL_DESCENDANTS).childDataNodes.size()
+            def numberOfChildrenBeforeDelete = objectUnderTest.getDataNodes(DATASPACE_NAME, ANCHOR_NAME3, pathToParentOfDeletedNode, INCLUDE_ALL_DESCENDANTS)[0].childDataNodes.size()
         when: 'target node is deleted'
             objectUnderTest.deleteDataNode(DATASPACE_NAME, ANCHOR_NAME3, deleteTarget)
         then: 'one child has been deleted'
-            def numberOfChildrenAfterDelete = objectUnderTest.getDataNode(DATASPACE_NAME, ANCHOR_NAME3, pathToParentOfDeletedNode, INCLUDE_ALL_DESCENDANTS).childDataNodes.size()
+            def numberOfChildrenAfterDelete = objectUnderTest.getDataNodes(DATASPACE_NAME, ANCHOR_NAME3, pathToParentOfDeletedNode, INCLUDE_ALL_DESCENDANTS)[0].childDataNodes.size()
             assert numberOfChildrenAfterDelete == numberOfChildrenBeforeDelete - 1
         where:
             scenario                | deleteTarget              | pathToParentOfDeletedNode
@@ -634,13 +642,13 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
         and: 'data nodes are deleted'
             objectUnderTest.deleteDataNode(DATASPACE_NAME, ANCHOR_NAME3, xpathForDeletion)
         when: 'verify data nodes are removed'
-            objectUnderTest.getDataNode(DATASPACE_NAME, ANCHOR_NAME3, xpathForDeletion, INCLUDE_ALL_DESCENDANTS)
+            objectUnderTest.getDataNodes(DATASPACE_NAME, ANCHOR_NAME3, xpathForDeletion, INCLUDE_ALL_DESCENDANTS)
         then:
             thrown(DataNodeNotFoundException)
         and: 'some related object is not deleted'
             if (xpathSurvivor!=null) {
-                dataNode = objectUnderTest.getDataNode(DATASPACE_NAME, ANCHOR_NAME3, xpathSurvivor, INCLUDE_ALL_DESCENDANTS)
-                assert dataNode.xpath == xpathSurvivor
+                dataNode = objectUnderTest.getDataNodes(DATASPACE_NAME, ANCHOR_NAME3, xpathSurvivor, INCLUDE_ALL_DESCENDANTS)
+                assert dataNode[0].xpath == xpathSurvivor
             }
         where: 'following parameters were used'
             scenario                               | xpathForDeletion                                  || xpathSurvivor
@@ -667,11 +675,23 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
     @Sql([CLEAR_DATA, SET_DATA])
     def 'Delete data node for an anchor.'() {
         given: 'a data-node exists for an anchor'
-            assert fragmentsExistInDB(DATASPACE_1001_ID, ANCHOR_3003_ID)
+            assert fragmentsExistInDB(1001, 3003)
         when: 'data nodes are deleted '
             objectUnderTest.deleteDataNodes(DATASPACE_NAME, ANCHOR_NAME3)
         then: 'all data-nodes are deleted successfully'
-            assert !fragmentsExistInDB(DATASPACE_1001_ID, ANCHOR_3003_ID)
+            assert !fragmentsExistInDB(1001, 3003)
+    }
+
+    @Sql([CLEAR_DATA, SET_DATA])
+    def 'Delete data node for multiple anchors.'() {
+        given: 'a data-node exists for an anchor'
+            assert fragmentsExistInDB(1001, 3001)
+            assert fragmentsExistInDB(1001, 3003)
+        when: 'data nodes are deleted '
+            objectUnderTest.deleteDataNodes(DATASPACE_NAME, ['ANCHOR-001', 'ANCHOR-003'])
+        then: 'all data-nodes are deleted successfully'
+            assert !fragmentsExistInDB(1001, 3001)
+            assert !fragmentsExistInDB(1001, 3003)
     }
 
     def fragmentsExistInDB(dataSpaceId, anchorId) {
@@ -711,6 +731,15 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
         return flatMap
     }
 
+    def static multipleTreesToFlatMapByXpath(Map<String, DataNode> flatMap, Collection<DataNode> dataNodeTrees) {
+        for (DataNode dataNodeTree: dataNodeTrees){
+            flatMap.put(dataNodeTree.xpath, dataNodeTree)
+            dataNodeTree.childDataNodes
+                .forEach(childDataNode -> multipleTreesToFlatMapByXpath(flatMap, [childDataNode]))
+        }
+        return flatMap
+    }
+
     def keysToXpaths(parent, Collection keys) {
         return keys.collect { "${parent}/child-list[@key='${it}']".toString() }
     }
index 5cabc85..ba42a08 100644 (file)
@@ -25,7 +25,6 @@ import org.hibernate.StaleStateException
 import org.onap.cps.spi.FetchDescendantsOption
 import org.onap.cps.spi.entities.AnchorEntity
 import org.onap.cps.spi.entities.FragmentEntity
-import org.onap.cps.spi.entities.FragmentExtract
 import org.onap.cps.spi.exceptions.ConcurrencyException
 import org.onap.cps.spi.exceptions.DataValidationException
 import org.onap.cps.spi.model.DataNode
@@ -89,15 +88,17 @@ class CpsDataPersistenceServiceSpec extends Specification {
     }
 
     def 'Handling of StaleStateException (caused by concurrent updates) during update data nodes and descendants.'() {
-        given: 'the system contains and can update one datanode'
-            def dataNode1 = createDataNodeAndMockRepositoryMethodSupportingIt('/node1', 'OK')
-        and: 'the system contains two more datanodes that throw an exception while updating'
-            def dataNode2 = createDataNodeAndMockRepositoryMethodSupportingIt('/node2', 'EXCEPTION')
-            def dataNode3 = createDataNodeAndMockRepositoryMethodSupportingIt('/node3', 'EXCEPTION')
+        given: 'the system can update one datanode and has two more datanodes that throw an exception while updating'
+            def dataNodes = createDataNodesAndMockRepositoryMethodSupportingThem([
+                '/node1': 'OK',
+                '/node2': 'EXCEPTION',
+                '/node3': 'EXCEPTION'])
+        and: 'db contains an anchor'
+            mockAnchorRepository.getByDataspaceAndName(*_) >> new AnchorEntity(id:123)
         and: 'the batch update will therefore also fail'
             mockFragmentRepository.saveAll(*_) >> { throw new StaleStateException("concurrent updates") }
         when: 'attempt batch update data nodes'
-            objectUnderTest.updateDataNodesAndDescendants('some-dataspace', 'some-anchor', [dataNode1, dataNode2, dataNode3])
+            objectUnderTest.updateDataNodesAndDescendants('some-dataspace', 'some-anchor', dataNodes)
         then: 'concurrency exception is thrown'
             def thrown = thrown(ConcurrencyException)
             assert thrown.message == 'Concurrent Transactions'
@@ -112,10 +113,10 @@ class CpsDataPersistenceServiceSpec extends Specification {
         given: 'the db has a fragment with an attribute property JSON value of #scenario'
             mockFragmentWithJson("{\"some attribute\": ${dataString}}")
         when: 'getting the data node represented by this fragment'
-            def dataNode = objectUnderTest.getDataNode('my-dataspace', 'my-anchor',
+            def dataNode = objectUnderTest.getDataNodes('my-dataspace', 'my-anchor',
                     '/parent-01', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
         then: 'the leaf is of the correct value and data type'
-            def attributeValue = dataNode.leaves.get('some attribute')
+            def attributeValue = dataNode[0].leaves.get('some attribute')
             assert attributeValue == expectedValue
             assert attributeValue.class == expectedDataClass
         where: 'the following Data Type is passed'
@@ -136,7 +137,7 @@ class CpsDataPersistenceServiceSpec extends Specification {
         given: 'a fragment with invalid JSON'
             mockFragmentWithJson('{invalid json')
         when: 'getting the data node represented by this fragment'
-            objectUnderTest.getDataNode('my-dataspace', 'my-anchor',
+            objectUnderTest.getDataNodes('my-dataspace', 'my-anchor',
                     '/parent-01', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
         then: 'a data validation exception is thrown'
             thrown(DataValidationException)
@@ -151,7 +152,7 @@ class CpsDataPersistenceServiceSpec extends Specification {
             def fragmentEntity2 = new FragmentEntity(xpath: '/xpath2', childFragments: [])
             mockFragmentRepository.findByAnchorAndMultipleCpsPaths(123, ['/xpath1', '/xpath2'] as Set<String>) >> [fragmentEntity1, fragmentEntity2]
         when: 'getting data nodes for 2 xpaths'
-            def result = objectUnderTest.getDataNodes('some-dataspace', 'some-anchor', ['/xpath1', '/xpath2'], FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
+            def result = objectUnderTest.getDataNodesForMultipleXpaths('some-dataspace', 'some-anchor', ['/xpath1', '/xpath2'], FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
         then: '2 data nodes are returned'
             assert result.size() == 2
     }
@@ -200,7 +201,9 @@ class CpsDataPersistenceServiceSpec extends Specification {
 
     def 'update data node and descendants: #scenario'(){
         given: 'mocked responses'
-            mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, '/test/xpath') >> new FragmentEntity(xpath: '/test/xpath', childFragments: [])
+            mockAnchorRepository.getByDataspaceAndName(_, _) >> new AnchorEntity(id:123)
+            mockFragmentRepository.findByAnchorAndMultipleCpsPaths(_, [] as Set) >> []
+            mockFragmentRepository.findByAnchorAndMultipleCpsPaths(_, ['/test/xpath'] as Set) >> [new FragmentEntity(xpath: '/test/xpath', childFragments: [])]
         when: 'replace data node tree'
             objectUnderTest.updateDataNodesAndDescendants('dataspaceName', 'anchorName', dataNodes)
         then: 'call fragment repository save all method'
@@ -212,9 +215,12 @@ class CpsDataPersistenceServiceSpec extends Specification {
     }
 
     def 'update data nodes and descendants'() {
-        given: 'the fragment repository returns a fragment entity related to the xpath input'
-            mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, '/test/xpath1') >> new FragmentEntity(xpath: '/test/xpath1', childFragments: [])
-            mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, '/test/xpath2') >> new FragmentEntity(xpath: '/test/xpath2', childFragments: [])
+        given: 'the fragment repository returns fragment entities related to the xpath inputs'
+            mockFragmentRepository.findByAnchorAndMultipleCpsPaths(_, ['/test/xpath1', '/test/xpath2'] as Set) >> [
+                new FragmentEntity(xpath: '/test/xpath1', childFragments: []),
+                new FragmentEntity(xpath: '/test/xpath2', childFragments: [])]
+        and: 'db contains an anchor'
+            mockAnchorRepository.getByDataspaceAndName(*_) >> new AnchorEntity(id:123)
         and: 'some data nodes with descendants'
             def dataNode1 = new DataNode(xpath: '/test/xpath1', leaves: ['id': 'testId1'], childDataNodes: [new DataNode(xpath: '/test/xpath1/child', leaves: ['id': 'childTestId1'])])
             def dataNode2 = new DataNode(xpath: '/test/xpath2', leaves: ['id': 'testId2'], childDataNodes: [new DataNode(xpath: '/test/xpath2/child', leaves: ['id': 'childTestId2'])])
@@ -240,13 +246,30 @@ class CpsDataPersistenceServiceSpec extends Specification {
         return dataNode
     }
 
+    def createDataNodesAndMockRepositoryMethodSupportingThem(Map<String, String> xpathToScenarioMap) {
+        def dataNodes = []
+        def fragmentEntities = []
+        xpathToScenarioMap.each {
+            def xpath = it.key
+            def scenario = it.value
+            def dataNode = new DataNodeBuilder().withXpath(xpath).build()
+            dataNodes.add(dataNode)
+            def fragmentEntity = new FragmentEntity(xpath: xpath, childFragments: [])
+            fragmentEntities.add(fragmentEntity)
+            mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, xpath) >> fragmentEntity
+            if ('EXCEPTION' == scenario) {
+                mockFragmentRepository.save(fragmentEntity) >> { throw new StaleStateException("concurrent updates") }
+            }
+        }
+        mockFragmentRepository.findByAnchorAndMultipleCpsPaths(_, xpathToScenarioMap.keySet()) >> fragmentEntities
+        return dataNodes
+    }
+
     def mockFragmentWithJson(json) {
         def anchorEntity = new AnchorEntity(id:123)
         mockAnchorRepository.getByDataspaceAndName(*_) >> anchorEntity
-        def mockFragmentExtract = Mock(FragmentExtract)
-        mockFragmentExtract.getId() >> 456
-        mockFragmentExtract.getAttributes() >> json
-        mockFragmentRepository.findByAnchorIdAndParentXpath(*_) >> [mockFragmentExtract]
+        def fragmentEntity = new FragmentEntity(xpath: '/parent-01', childFragments: [], attributes: json)
+        mockFragmentRepository.findByAnchorAndMultipleCpsPaths(123, ['/parent-01'] as Set<String>) >> [fragmentEntity]
     }
 
 }
index b67a5cc..daa7746 100644 (file)
@@ -75,12 +75,18 @@ class CpsPersistencePerfSpecBase extends CpsPersistenceSpecBase {
         return grandChildren
     }
 
-    def countDataNodes(dataNodes) {
-        int nodeCount = 1
+    def countDataNodes(Collection<DataNode> dataNodes) {
+        int nodeCount = 0
         for (DataNode parent : dataNodes) {
-            for (DataNode child : parent.childDataNodes) {
-                nodeCount = nodeCount + (countDataNodes(child))
-            }
+            nodeCount = nodeCount + countDataNodes(parent)
+        }
+        return nodeCount
+    }
+
+    def countDataNodes(DataNode dataNode) {
+        int nodeCount = 1
+        for (DataNode child : dataNode.childDataNodes) {
+            nodeCount = nodeCount + countDataNodes(child)
         }
         return nodeCount
     }
index 1ecad4e..30ff11b 100644 (file)
@@ -3,6 +3,7 @@
  *  Copyright (C) 2021-2022 Nordix Foundation
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2021 Bell Canada.
+ *  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.
@@ -60,13 +61,14 @@ class CpsPersistenceSpecBase extends Specification {
 
     static final String CLEAR_DATA = '/data/clear-all.sql'
 
-    static final String DATASPACE_NAME = 'DATASPACE-001'
-    static final String SCHEMA_SET_NAME1 = 'SCHEMA-SET-001'
-    static final String SCHEMA_SET_NAME2 = 'SCHEMA-SET-002'
-    static final String ANCHOR_NAME1 = 'ANCHOR-001'
-    static final String ANCHOR_NAME2 = 'ANCHOR-002'
-    static final String ANCHOR_NAME3 = 'ANCHOR-003'
-    static final String ANCHOR_FOR_DATA_NODES_WITH_LEAVES = 'ANCHOR-003'
-    static final String ANCHOR_FOR_SHOP_EXAMPLE = 'ANCHOR-004'
-    static final String ANCHOR_HAVING_SINGLE_TOP_LEVEL_FRAGMENT = 'ANCHOR-005'
+    static def DATASPACE_NAME = 'DATASPACE-001'
+    static def SCHEMA_SET_NAME1 = 'SCHEMA-SET-001'
+    static def SCHEMA_SET_NAME2 = 'SCHEMA-SET-002'
+    static def ANCHOR_NAME1 = 'ANCHOR-001'
+    static def ANCHOR_NAME2 = 'ANCHOR-002'
+    static def ANCHOR_NAME3 = 'ANCHOR-003'
+    static def ANCHOR_FOR_DATA_NODES_WITH_LEAVES = 'ANCHOR-003'
+    static def ANCHOR_FOR_SHOP_EXAMPLE = 'ANCHOR-004'
+    static def ANCHOR_HAVING_SINGLE_TOP_LEVEL_FRAGMENT = 'ANCHOR-005'
+    static def ANCHOR_WITH_MULTIPLE_TOP_LEVEL_FRAGMENTS = 'ANCHOR-006'
 }
index 3b9338c..eb138b9 100644 (file)
@@ -24,15 +24,12 @@ import org.onap.cps.spi.CpsDataPersistenceService
 import org.onap.cps.spi.impl.CpsPersistencePerfSpecBase
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.test.context.jdbc.Sql
-import org.springframework.util.StopWatch
 
 class CpsDataPersistenceServiceDeletePerfTest extends CpsPersistencePerfSpecBase {
 
     @Autowired
     CpsDataPersistenceService objectUnderTest
 
-    def stopWatch = new StopWatch()
-
     @Sql([CLEAR_DATA, PERF_TEST_DATA])
     def 'Create a node with many descendants (please note, subsequent tests depend on this running first).'() {
         when: 'a node with a large number of descendants is created'
@@ -53,8 +50,8 @@ class CpsDataPersistenceServiceDeletePerfTest extends CpsPersistencePerfSpecBase
             }
             stopWatch.stop()
             def deleteDurationInMillis = stopWatch.getTotalTimeMillis()
-        then: 'delete duration is under 350 milliseconds'
-            recordAndAssertPerformance('Delete 5 children', 350, deleteDurationInMillis)
+        then: 'delete duration is under 300 milliseconds'
+            recordAndAssertPerformance('Delete 5 children', 300, deleteDurationInMillis)
     }
 
     def 'Batch delete 100 children with grandchildren'() {
@@ -67,8 +64,8 @@ class CpsDataPersistenceServiceDeletePerfTest extends CpsPersistencePerfSpecBase
             objectUnderTest.deleteDataNodes(PERF_DATASPACE, PERF_ANCHOR, xpathsToDelete)
             stopWatch.stop()
             def deleteDurationInMillis = stopWatch.getTotalTimeMillis()
-        then: 'delete duration is under 350 milliseconds'
-            recordAndAssertPerformance('Batch delete 100 children', 350, deleteDurationInMillis)
+        then: 'delete duration is under 250 milliseconds'
+            recordAndAssertPerformance('Batch delete 100 children', 250, deleteDurationInMillis)
     }
 
     def 'Delete 50 grandchildren (that have no descendants)'() {
@@ -80,8 +77,8 @@ class CpsDataPersistenceServiceDeletePerfTest extends CpsPersistencePerfSpecBase
             }
             stopWatch.stop()
             def deleteDurationInMillis = stopWatch.getTotalTimeMillis()
-        then: 'delete duration is under 350 milliseconds'
-            recordAndAssertPerformance('Delete 50 grandchildren', 350, deleteDurationInMillis)
+        then: 'delete duration is under 300 milliseconds'
+            recordAndAssertPerformance('Delete 50 grandchildren', 300, deleteDurationInMillis)
     }
 
     def 'Batch delete 500 grandchildren (that have no descendants)'() {
@@ -97,8 +94,8 @@ class CpsDataPersistenceServiceDeletePerfTest extends CpsPersistencePerfSpecBase
             objectUnderTest.deleteDataNodes(PERF_DATASPACE, PERF_ANCHOR, xpathsToDelete)
             stopWatch.stop()
             def deleteDurationInMillis = stopWatch.getTotalTimeMillis()
-        then: 'delete duration is under 350 milliseconds'
-            recordAndAssertPerformance('Batch delete 500 grandchildren', 350, deleteDurationInMillis)
+        then: 'delete duration is under 75 milliseconds'
+            recordAndAssertPerformance('Batch delete 500 grandchildren', 75, deleteDurationInMillis)
     }
 
     @Sql([CLEAR_DATA, PERF_TEST_DATA])
@@ -108,8 +105,8 @@ class CpsDataPersistenceServiceDeletePerfTest extends CpsPersistencePerfSpecBase
             createLineage(objectUnderTest, 150, 50, true)
             stopWatch.stop()
             def setupDurationInMillis = stopWatch.getTotalTimeMillis()
-        then: 'setup duration is under 10 seconds'
-            recordAndAssertPerformance('Setup lists', 10_000, setupDurationInMillis)
+        then: 'setup duration is under 5 seconds'
+            recordAndAssertPerformance('Setup lists', 5_000, setupDurationInMillis)
     }
 
     def 'Delete 5 whole lists'() {
@@ -121,8 +118,8 @@ class CpsDataPersistenceServiceDeletePerfTest extends CpsPersistencePerfSpecBase
             }
             stopWatch.stop()
             def deleteDurationInMillis = stopWatch.getTotalTimeMillis()
-        then: 'delete duration is under 1500 milliseconds'
-            recordAndAssertPerformance('Delete 5 whole lists', 1500, deleteDurationInMillis)
+        then: 'delete duration is under 1300 milliseconds'
+            recordAndAssertPerformance('Delete 5 whole lists', 1300, deleteDurationInMillis)
     }
 
     def 'Batch delete 100 whole lists'() {
@@ -135,8 +132,8 @@ class CpsDataPersistenceServiceDeletePerfTest extends CpsPersistencePerfSpecBase
             objectUnderTest.deleteDataNodes(PERF_DATASPACE, PERF_ANCHOR, xpathsToDelete)
             stopWatch.stop()
             def deleteDurationInMillis = stopWatch.getTotalTimeMillis()
-        then: 'delete duration is under 350 milliseconds'
-            recordAndAssertPerformance('Batch delete 100 whole lists', 350, deleteDurationInMillis)
+        then: 'delete duration is under 500 milliseconds'
+            recordAndAssertPerformance('Batch delete 100 whole lists', 500, deleteDurationInMillis)
     }
 
     def 'Delete 10 list elements'() {
@@ -148,8 +145,8 @@ class CpsDataPersistenceServiceDeletePerfTest extends CpsPersistencePerfSpecBase
             }
             stopWatch.stop()
             def deleteDurationInMillis = stopWatch.getTotalTimeMillis()
-        then: 'delete duration is under 750 milliseconds'
-            recordAndAssertPerformance('Delete 10 lists elements', 750, deleteDurationInMillis)
+        then: 'delete duration is under 600 milliseconds'
+            recordAndAssertPerformance('Delete 10 lists elements', 600, deleteDurationInMillis)
     }
 
     def 'Batch delete 500 list elements'() {
@@ -165,8 +162,8 @@ class CpsDataPersistenceServiceDeletePerfTest extends CpsPersistencePerfSpecBase
             objectUnderTest.deleteDataNodes(PERF_DATASPACE, PERF_ANCHOR, xpathsToDelete)
             stopWatch.stop()
             def deleteDurationInMillis = stopWatch.getTotalTimeMillis()
-        then: 'delete duration is under 350 milliseconds'
-            recordAndAssertPerformance('Batch delete 500 lists elements', 350, deleteDurationInMillis)
+        then: 'delete duration is under 60 milliseconds'
+            recordAndAssertPerformance('Batch delete 500 lists elements', 60, deleteDurationInMillis)
     }
 
     @Sql([CLEAR_DATA, PERF_TEST_DATA])
@@ -193,8 +190,8 @@ class CpsDataPersistenceServiceDeletePerfTest extends CpsPersistencePerfSpecBase
             objectUnderTest.deleteDataNodes(PERF_DATASPACE, PERF_ANCHOR, [PERF_TEST_PARENT])
             stopWatch.stop()
             def deleteDurationInMillis = stopWatch.getTotalTimeMillis()
-        then: 'delete duration is under 300 milliseconds'
-            recordAndAssertPerformance('Batch delete one large node', 300, deleteDurationInMillis)
+        then: 'delete duration is under 200 milliseconds'
+            recordAndAssertPerformance('Batch delete one large node', 200, deleteDurationInMillis)
     }
 
     @Sql([CLEAR_DATA, PERF_TEST_DATA])
@@ -207,12 +204,12 @@ class CpsDataPersistenceServiceDeletePerfTest extends CpsPersistencePerfSpecBase
             objectUnderTest.deleteDataNode(PERF_DATASPACE, PERF_ANCHOR, '/')
             stopWatch.stop()
             def deleteDurationInMillis = stopWatch.getTotalTimeMillis()
-        then: 'delete duration is under 300 milliseconds'
-            recordAndAssertPerformance('Delete root node', 300, deleteDurationInMillis)
+        then: 'delete duration is under 250 milliseconds'
+            recordAndAssertPerformance('Delete root node', 250, deleteDurationInMillis)
     }
 
     @Sql([CLEAR_DATA, PERF_TEST_DATA])
-    def 'Delete data nodes for an anchor'() {212
+    def 'Delete data nodes for an anchor'() {
         given: 'a node with a large number of descendants is created'
             createLineage(objectUnderTest, 50, 50, false)
             createLineage(objectUnderTest, 50, 50, true)
@@ -221,8 +218,22 @@ class CpsDataPersistenceServiceDeletePerfTest extends CpsPersistencePerfSpecBase
             objectUnderTest.deleteDataNodes(PERF_DATASPACE, PERF_ANCHOR)
             stopWatch.stop()
             def deleteDurationInMillis = stopWatch.getTotalTimeMillis()
-        then: 'delete duration is under 300 milliseconds'
-            recordAndAssertPerformance('Delete data nodes for anchor', 300, deleteDurationInMillis)
+        then: 'delete duration is under 250 milliseconds'
+            recordAndAssertPerformance('Delete data nodes for anchor', 250, deleteDurationInMillis)
+    }
+
+    @Sql([CLEAR_DATA, PERF_TEST_DATA])
+    def 'Delete data nodes for multiple anchors'() {
+        given: 'a node with a large number of descendants is created'
+            createLineage(objectUnderTest, 50, 50, false)
+            createLineage(objectUnderTest, 50, 50, true)
+        when: 'data nodes are deleted'
+            stopWatch.start()
+            objectUnderTest.deleteDataNodes(PERF_DATASPACE, [PERF_ANCHOR])
+            stopWatch.stop()
+            def deleteDurationInMillis = stopWatch.getTotalTimeMillis()
+        then: 'delete duration is under 250 milliseconds'
+            recordAndAssertPerformance('Delete data nodes for anchors', 250, deleteDurationInMillis)
     }
 
 }
index 0c4f5ec..3562419 100644 (file)
@@ -21,7 +21,6 @@
 package org.onap.cps.spi.performance
 
 import org.onap.cps.spi.impl.CpsPersistencePerfSpecBase
-import org.springframework.util.StopWatch
 import org.onap.cps.spi.CpsDataPersistenceService
 import org.onap.cps.spi.repository.AnchorRepository
 import org.onap.cps.spi.repository.DataspaceRepository
@@ -64,17 +63,17 @@ class CpsDataPersistenceServicePerfTest extends CpsPersistencePerfSpecBase {
     def 'Get data node with many descendants by xpath #scenario'() {
         when: 'get parent is executed with all descendants'
             stopWatch.start()
-            def result = objectUnderTest.getDataNode(PERF_DATASPACE, PERF_ANCHOR, xpath, INCLUDE_ALL_DESCENDANTS)
+            def result = objectUnderTest.getDataNodes(PERF_DATASPACE, PERF_ANCHOR, xpath, INCLUDE_ALL_DESCENDANTS)
             stopWatch.stop()
             def readDurationInMillis = stopWatch.getTotalTimeMillis()
-        then: 'read duration is under 500 milliseconds'
-            recordAndAssertPerformance("Get ${scenario}", 500, readDurationInMillis)
+        then: 'read duration is under #allowedDuration milliseconds'
+            recordAndAssertPerformance("Get ${scenario}", allowedDuration, readDurationInMillis)
         and: 'data node is returned with all the descendants populated'
-            assert countDataNodes(result) == TOTAL_NUMBER_OF_NODES
+            assert countDataNodes(result[0]) == TOTAL_NUMBER_OF_NODES
         where: 'the following xPaths are used'
-            scenario || xpath
-            'parent' || PERF_TEST_PARENT
-            'root'   || ''
+            scenario | xpath            || allowedDuration
+            'parent' | PERF_TEST_PARENT || 3500
+            'root'   | ''               || 500
     }
 
     def 'Query parent data node with many descendants by cps-path'() {
@@ -93,13 +92,13 @@ class CpsDataPersistenceServicePerfTest extends CpsPersistencePerfSpecBase {
         when: 'we query for all grandchildren (except 1 for fun) with the new native method'
             xpathsToAllGrandChildren.remove(0)
             stopWatch.start()
-            def result = objectUnderTest.getDataNodes(PERF_DATASPACE, PERF_ANCHOR, xpathsToAllGrandChildren, INCLUDE_ALL_DESCENDANTS)
+            def result = objectUnderTest.getDataNodesForMultipleXpaths(PERF_DATASPACE, PERF_ANCHOR, xpathsToAllGrandChildren, INCLUDE_ALL_DESCENDANTS)
             stopWatch.stop()
             def readDurationInMillis = stopWatch.getTotalTimeMillis()
         then: 'the returned number of entities equal to the number of children * number of grandchildren'
             assert result.size() == xpathsToAllGrandChildren.size()
-        and: 'it took less then 4000ms'
-            recordAndAssertPerformance('Find multiple xpaths', 4000, readDurationInMillis)
+        and: 'it took less then 3000ms'
+            recordAndAssertPerformance('Find multiple xpaths', 3000, readDurationInMillis)
     }
 
     def 'Query many descendants by cps-path with #scenario'() {
@@ -109,7 +108,6 @@ class CpsDataPersistenceServicePerfTest extends CpsPersistencePerfSpecBase {
             stopWatch.stop()
             def readDurationInMillis = stopWatch.getTotalTimeMillis()
         then: 'read duration is under #allowedDuration milliseconds'
-            assert readDurationInMillis < allowedDuration
             recordAndAssertPerformance("Query many descendants by cpspath (${scenario})", allowedDuration, readDurationInMillis)
         and: 'data node is returned with all the descendants populated'
             assert result.size() == NUMBER_OF_CHILDREN
@@ -118,4 +116,43 @@ class CpsDataPersistenceServicePerfTest extends CpsPersistencePerfSpecBase {
             'omit descendants                             ' | OMIT_DESCENDANTS         || 150
             'include descendants (although there are none)' | INCLUDE_ALL_DESCENDANTS  || 150
     }
+
+    def 'Update data nodes with descendants'() {
+        given: 'a list of xpaths to data nodes with descendants (xpath for each child)'
+            def xpaths = (1..20).collect {
+                "${PERF_TEST_PARENT}/perf-test-child-${it}".toString()
+            }
+        and: 'the correct number of data nodes are fetched'
+            def dataNodes = objectUnderTest.getDataNodesForMultipleXpaths(PERF_DATASPACE, PERF_ANCHOR, xpaths, INCLUDE_ALL_DESCENDANTS)
+            assert dataNodes.size() == 20
+            assert countDataNodes(dataNodes) == 20 + 20 * 50
+        when: 'the fragment entities are updated by the data nodes'
+            stopWatch.start()
+            objectUnderTest.updateDataNodesAndDescendants(PERF_DATASPACE, PERF_ANCHOR, dataNodes)
+            stopWatch.stop()
+            def updateDurationInMillis = stopWatch.getTotalTimeMillis()
+        then: 'update duration is under 600 milliseconds'
+            recordAndAssertPerformance('Update data nodes with descendants', 600, updateDurationInMillis)
+    }
+
+    def 'Update data nodes without descendants'() {
+        given: 'a list of xpaths to data nodes without descendants (xpath for each grandchild)'
+            def xpaths = []
+            for (int childIndex = 21; childIndex <= 40; childIndex++) {
+                xpaths.addAll((1..50).collect {
+                    "${PERF_TEST_PARENT}/perf-test-child-${childIndex}/perf-test-grand-child-${it}".toString()
+                })
+            }
+        and: 'the correct number of data nodes are fetched'
+            def dataNodes = objectUnderTest.getDataNodesForMultipleXpaths(PERF_DATASPACE, PERF_ANCHOR, xpaths, OMIT_DESCENDANTS)
+            assert dataNodes.size() == 20 * 50
+            assert countDataNodes(dataNodes) == 20 * 50
+        when: 'the fragment entities are updated by the data nodes'
+            stopWatch.start()
+            objectUnderTest.updateDataNodesAndDescendants(PERF_DATASPACE, PERF_ANCHOR, dataNodes)
+            stopWatch.stop()
+            def updateDurationInMillis = stopWatch.getTotalTimeMillis()
+        then: 'update duration is under 600 milliseconds'
+            recordAndAssertPerformance('Update data nodes without descendants', 600, updateDurationInMillis)
+    }
 }
index 40fc44c..2ab7966 100644 (file)
@@ -1,7 +1,7 @@
 /*
    ============LICENSE_START=======================================================
     Copyright (C) 2020 Pantheon.tech
-    Modifications Copyright (C) 2020 Nordix Foundation.
+    Modifications Copyright (C) 2020-2023 Nordix Foundation.
     Modifications Copyright (C) 2021-2022 Bell Canada.
    ================================================================================
    Licensed under the Apache License, Version 2.0 (the "License");
@@ -32,7 +32,8 @@ INSERT INTO SCHEMA_SET (ID, NAME, DATASPACE_ID) VALUES
 
 INSERT INTO ANCHOR (ID, NAME, DATASPACE_ID, SCHEMA_SET_ID) VALUES
     (3001, 'ANCHOR-001', 1001, 2001),
-    (3002, 'ANCHOR-002', 1001, 2002);
+    (3002, 'ANCHOR-002', 1001, 2002),
+    (3003, 'ANCHOR-003', 1001, 2002);
 
 INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH, ATTRIBUTES) VALUES
     (4001, 1001, 3001, null, '/xpath', '{}');
index ad463cf..caafcd3 100755 (executable)
@@ -52,7 +52,8 @@ INSERT INTO ANCHOR (ID, NAME, DATASPACE_ID, SCHEMA_SET_ID) VALUES
     (3001, 'ANCHOR-001', 1001, 2001),
     (3003, 'ANCHOR-003', 1001, 2001),
     (3004, 'ncmp-dmi-registry', 1002, 2001),
-    (3005, 'ANCHOR-005', 1001, 2001);
+    (3005, 'ANCHOR-005', 1001, 2001),
+    (3006, 'ANCHOR-006', 1001, 2001);
 
 INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH) VALUES
     (4001, 1001, 3001, null, '/parent-1'),
@@ -68,6 +69,16 @@ INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH, ATTRIBUTES)
     (5011, 1001, 3005, 5009, '/parent-207/child-002', '{"second-child-leaf": "second-child-leaf value"}'),
     (5012, 1001, 3005, 5011, '/parent-207/child-002/grand-child', '{"grand-child-leaf": "grand-child-leaf value"}');
 
+INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH, ATTRIBUTES) VALUES
+    (5013, 1001, 3006, null, '/parent-208', '{"parent-leaf-1": "parent-leaf value-1"}'),
+    (5014, 1001, 3006, 5013, '/parent-208/child-001', '{"first-child-leaf": "first-child-leaf value"}'),
+    (5015, 1001, 3006, 5013, '/parent-208/child-002', '{"second-child-leaf": "second-child-leaf value"}'),
+    (5016, 1001, 3006, 5015, '/parent-208/child-002/grand-child', '{"grand-child-leaf": "grand-child-leaf value"}'),
+    (5017, 1001, 3006, null, '/parent-209', '{"parent-leaf-2": "parent-leaf value-2"}'),
+    (5018, 1001, 3006, 5017, '/parent-209/child-001', '{"first-child-leaf": "first-child-leaf value"}'),
+    (5019, 1001, 3006, 5017, '/parent-209/child-002', '{"second-child-leaf": "second-child-leaf value"}'),
+    (5020, 1001, 3006, 5019, '/parent-209/child-002/grand-child', '{"grand-child-leaf": "grand-child-leaf value"}');
+
 INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH, ATTRIBUTES) VALUES
     (4201, 1001, 3003, null, '/parent-200', '{"leaf-value": "original"}'),
     (4202, 1001, 3003, 4201, '/parent-200/child-201', '{"leaf-value": "original"}'),
index 80bf076..2019f6f 100644 (file)
@@ -29,7 +29,7 @@
   <parent>
     <groupId>org.onap.cps</groupId>
     <artifactId>cps-parent</artifactId>
-    <version>3.2.3-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
     <relativePath>../cps-parent/pom.xml</relativePath>
   </parent>
 
index b0e68cf..fcf3f54 100755 (executable)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2020-2022 Nordix Foundation
+ *  Copyright (C) 2020-2023 Nordix Foundation
  *  Modifications Copyright (C) 2020-2022 Bell Canada.
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2022 TechMahindra Ltd.
@@ -84,7 +84,7 @@ public interface CpsAdminService {
     Collection<Anchor> getAnchors(String dataspaceName);
 
     /**
-     * Read all anchors associated the given schema-set in the given dataspace.
+     * Read all anchors associated with the given schema-set in the given dataspace.
      *
      * @param dataspaceName dataspace name
      * @param schemaSetName schema-set name
@@ -92,6 +92,15 @@ public interface CpsAdminService {
      */
     Collection<Anchor> getAnchors(String dataspaceName, String schemaSetName);
 
+    /**
+     * Read all anchors associated with the given schema-sets in the given dataspace.
+     *
+     * @param dataspaceName  dataspace name
+     * @param schemaSetNames schema-set names
+     * @return a collection of anchors
+     */
+    Collection<Anchor> getAnchors(String dataspaceName, Collection<String> schemaSetNames);
+
     /**
      * Get an anchor in the given dataspace using the anchor name.
      *
@@ -109,6 +118,14 @@ public interface CpsAdminService {
      */
     void deleteAnchor(String dataspaceName, String anchorName);
 
+    /**
+     * Delete anchors by name in given dataspace.
+     *
+     * @param dataspaceName dataspace name
+     * @param anchorNames   anchor names
+     */
+    void deleteAnchors(String dataspaceName, Collection<String> anchorNames);
+
     /**
      * Query anchor names for the given module names in the provided dataspace.
      *
index 174d71f..39fa45a 100644 (file)
@@ -4,6 +4,7 @@
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2021-2022 Bell Canada
  *  Modifications Copyright (C) 2022 Deutsche Telekom AG
+ *  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.
@@ -110,30 +111,31 @@ public interface CpsDataService {
             Collection<String> jsonDataList, OffsetDateTime observedTimestamp);
 
     /**
-     * Retrieves datanode by XPath for given dataspace and anchor.
+     * Retrieves all the datanodes by XPath for given dataspace and anchor.
      *
-     * @param dataspaceName          dataspace name
-     * @param anchorName             anchor name
-     * @param xpath                  xpath
-     * @param fetchDescendantsOption defines the scope of data to fetch: either single node or all the descendant nodes
-     *                               (recursively) as well
-     * @return data node object
+     * @param dataspaceName           dataspace name
+     * @param anchorName              anchor name
+     * @param xpath                   xpath
+     * @param fetchDescendantsOption  defines the scope of data to fetch: either single node or all the descendant nodes
+     *                                (recursively) as well
+     * @return collection of data node objects
      */
-    DataNode getDataNode(String dataspaceName, String anchorName, String xpath,
-        FetchDescendantsOption fetchDescendantsOption);
+    Collection<DataNode> getDataNodes(String dataspaceName, String anchorName, String xpath,
+                                      FetchDescendantsOption fetchDescendantsOption);
 
     /**
-     * Retrieves datanodes by XPath for given dataspace and anchor.
+     * Retrieves all the datanodes for multiple XPaths for given dataspace and anchor.
      *
-     * @param dataspaceName          dataspace name
-     * @param anchorName             anchor name
-     * @param xpaths                 collection of xpath
-     * @param fetchDescendantsOption defines the scope of data to fetch: either single node or all the descendant nodes
-     *                               (recursively) as well
-     * @return data node object
+     * @param dataspaceName           dataspace name
+     * @param anchorName              anchor name
+     * @param xpaths                  collection of xpaths
+     * @param fetchDescendantsOption  defines the scope of data to fetch: either single node or all the descendant nodes
+     *                                (recursively) as well
+     * @return collection of data node objects
      */
-    Collection<DataNode> getDataNodes(String dataspaceName, String anchorName, Collection<String> xpaths,
-                         FetchDescendantsOption fetchDescendantsOption);
+    Collection<DataNode> getDataNodesForMultipleXpaths(String dataspaceName, String anchorName,
+                                                       Collection<String> xpaths,
+                                                       FetchDescendantsOption fetchDescendantsOption);
 
     /**
      * Updates data node for given dataspace and anchor using xpath to parent node.
@@ -227,6 +229,15 @@ public interface CpsDataService {
      */
     void deleteDataNodes(String dataspaceName, String anchorName, OffsetDateTime observedTimestamp);
 
+    /**
+     * Deletes all data nodes for multiple anchors in a dataspace.
+     *
+     * @param dataspaceName     dataspace name
+     * @param anchorNames       anchor names
+     * @param observedTimestamp observed timestamp
+     */
+    void deleteDataNodes(String dataspaceName, Collection<String> anchorNames, OffsetDateTime observedTimestamp);
+
     /**
      * Deletes a list or a list-element under given anchor and dataspace.
      *
index ece3eb9..e286eea 100755 (executable)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2020-2022 Nordix Foundation
+ *  Copyright (C) 2020-2023 Nordix Foundation
  *  Modifications Copyright (C) 2020-2022 Bell Canada.
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2022 TechMahindra Ltd.
@@ -86,6 +86,13 @@ public class CpsAdminServiceImpl implements CpsAdminService {
         return cpsAdminPersistenceService.getAnchors(dataspaceName, schemaSetName);
     }
 
+    @Override
+    public Collection<Anchor> getAnchors(final String dataspaceName, final Collection<String> schemaSetNames) {
+        cpsValidator.validateNameCharacters(dataspaceName);
+        cpsValidator.validateNameCharacters(schemaSetNames);
+        return cpsAdminPersistenceService.getAnchors(dataspaceName, schemaSetNames);
+    }
+
     @Override
     public Anchor getAnchor(final String dataspaceName, final String anchorName) {
         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
@@ -99,6 +106,14 @@ public class CpsAdminServiceImpl implements CpsAdminService {
         cpsAdminPersistenceService.deleteAnchor(dataspaceName, anchorName);
     }
 
+    @Override
+    public void deleteAnchors(final String dataspaceName, final Collection<String> anchorNames) {
+        cpsValidator.validateNameCharacters(dataspaceName);
+        cpsValidator.validateNameCharacters(anchorNames);
+        cpsDataService.deleteDataNodes(dataspaceName, anchorNames, OffsetDateTime.now());
+        cpsAdminPersistenceService.deleteAnchors(dataspaceName, anchorNames);
+    }
+
     @Override
     public Collection<String> queryAnchorNames(final String dataspaceName, final Collection<String> moduleNames) {
         cpsValidator.validateNameCharacters(dataspaceName);
index 06a0845..721d4a9 100755 (executable)
@@ -3,7 +3,7 @@
  *  Copyright (C) 2021-2023 Nordix Foundation
  *  Modifications Copyright (C) 2020-2022 Bell Canada.
  *  Modifications Copyright (C) 2021 Pantheon.tech
- *  Modifications Copyright (C) 2022 TechMahindra Ltd.
+ *  Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
  *  Modifications Copyright (C) 2022 Deutsche Telekom AG
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -134,21 +134,23 @@ public class CpsDataServiceImpl implements CpsDataService {
 
     @Override
     @Timed(value = "cps.data.service.datanode.get",
-        description = "Time taken to get a data node")
-    public DataNode getDataNode(final String dataspaceName, final String anchorName, final String xpath,
-        final FetchDescendantsOption fetchDescendantsOption) {
+            description = "Time taken to get data nodes for an xpath")
+    public Collection<DataNode> getDataNodes(final String dataspaceName, final String anchorName,
+                                             final String xpath,
+                                             final FetchDescendantsOption fetchDescendantsOption) {
         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
-        return cpsDataPersistenceService.getDataNode(dataspaceName, anchorName, xpath, fetchDescendantsOption);
+        return cpsDataPersistenceService.getDataNodes(dataspaceName, anchorName, xpath, fetchDescendantsOption);
     }
 
     @Override
     @Timed(value = "cps.data.service.datanode.batch.get",
         description = "Time taken to get a batch of data nodes")
-    public Collection<DataNode> getDataNodes(final String dataspaceName, final String anchorName,
-                                             final Collection<String> xpaths,
-                                final FetchDescendantsOption fetchDescendantsOption) {
+    public Collection<DataNode> getDataNodesForMultipleXpaths(final String dataspaceName, final String anchorName,
+                                                              final Collection<String> xpaths,
+                                                              final FetchDescendantsOption fetchDescendantsOption) {
         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
-        return cpsDataPersistenceService.getDataNodes(dataspaceName, anchorName, xpaths, fetchDescendantsOption);
+        return cpsDataPersistenceService.getDataNodesForMultipleXpaths(dataspaceName, anchorName, xpaths,
+                fetchDescendantsOption);
     }
 
     @Override
@@ -272,8 +274,8 @@ public class CpsDataServiceImpl implements CpsDataService {
     }
 
     @Override
-    @Timed(value = "cps.data.service.datanode.all.delete",
-        description = "Time taken to delete all datanodes")
+    @Timed(value = "cps.data.service.datanode.delete.anchor",
+        description = "Time taken to delete all datanodes for an anchor")
     public void deleteDataNodes(final String dataspaceName, final String anchorName,
                                 final OffsetDateTime observedTimestamp) {
         cpsValidator.validateNameCharacters(dataspaceName, anchorName);
@@ -281,6 +283,19 @@ public class CpsDataServiceImpl implements CpsDataService {
         cpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName);
     }
 
+    @Override
+    @Timed(value = "cps.data.service.datanode.delete.anchor.batch",
+        description = "Time taken to delete all datanodes for multiple anchors")
+    public void deleteDataNodes(final String dataspaceName, final Collection<String> anchorNames,
+                                final OffsetDateTime observedTimestamp) {
+        cpsValidator.validateNameCharacters(dataspaceName);
+        cpsValidator.validateNameCharacters(anchorNames);
+        for (final String anchorName : anchorNames) {
+            processDataUpdatedEventAsync(dataspaceName, anchorName, ROOT_NODE_XPATH, DELETE, observedTimestamp);
+        }
+        cpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorNames);
+    }
+
     @Override
     @Timed(value = "cps.data.service.list.delete",
         description = "Time taken to delete a list or list element")
index e71e6ce..d6c01f7 100644 (file)
@@ -26,6 +26,7 @@ package org.onap.cps.api.impl;
 import io.micrometer.core.annotation.Timed;
 import java.util.Collection;
 import java.util.Map;
+import java.util.stream.Collectors;
 import lombok.RequiredArgsConstructor;
 import org.onap.cps.api.CpsAdminService;
 import org.onap.cps.api.CpsModuleService;
@@ -114,12 +115,9 @@ public class CpsModuleServiceImpl implements CpsModuleService {
     public void deleteSchemaSetsWithCascade(final String dataspaceName, final Collection<String> schemaSetNames) {
         cpsValidator.validateNameCharacters(dataspaceName);
         cpsValidator.validateNameCharacters(schemaSetNames);
-        for (final String schemaSetName : schemaSetNames) {
-            final Collection<Anchor> anchors = cpsAdminService.getAnchors(dataspaceName, schemaSetName);
-            for (final Anchor anchor : anchors) {
-                cpsAdminService.deleteAnchor(dataspaceName, anchor.getName());
-            }
-        }
+        final Collection<String> anchorNames = cpsAdminService.getAnchors(dataspaceName, schemaSetNames)
+            .stream().map(Anchor::getName).collect(Collectors.toSet());
+        cpsAdminService.deleteAnchors(dataspaceName, anchorNames);
         cpsModulePersistenceService.deleteUnusedYangResourceModules();
         cpsModulePersistenceService.deleteSchemaSets(dataspaceName, schemaSetNames);
         for (final String schemaSetName : schemaSetNames) {
index f0cdaee..38f8988 100644 (file)
@@ -2,6 +2,7 @@
  * ============LICENSE_START=======================================================
  * Copyright (c) 2021-2022 Bell Canada.
  * Modifications Copyright (c) 2022 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.
@@ -76,8 +77,8 @@ public class CpsDataUpdatedEventFactory {
     public CpsDataUpdatedEvent createCpsDataUpdatedEvent(final Anchor anchor,
         final OffsetDateTime observedTimestamp, final Operation operation) {
         final var dataNode = (operation == Operation.DELETE) ? null :
-            cpsDataService.getDataNode(anchor.getDataspaceName(), anchor.getName(),
-                "/", FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS);
+            cpsDataService.getDataNodes(anchor.getDataspaceName(), anchor.getName(),
+                "/", FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS).iterator().next();
         return toCpsDataUpdatedEvent(anchor, dataNode, observedTimestamp, operation);
     }
 
index 6bcb698..1c1e80a 100755 (executable)
@@ -73,7 +73,7 @@ public interface CpsAdminPersistenceService {
     void createAnchor(String dataspaceName, String schemaSetName, String anchorName);
 
     /**
-     * Read all anchors associated the given schema-set in the given dataspace.
+     * Read all anchors associated with the given schema-set in the given dataspace.
      *
      * @param dataspaceName dataspace name
      * @param schemaSetName schema-set name
@@ -81,6 +81,15 @@ public interface CpsAdminPersistenceService {
      */
     Collection<Anchor> getAnchors(String dataspaceName, String schemaSetName);
 
+    /**
+     * Read all anchors associated with multiple schema-sets in the given dataspace.
+     *
+     * @param dataspaceName  dataspace name
+     * @param schemaSetNames schema-set names
+     * @return a collection of anchors
+     */
+    Collection<Anchor> getAnchors(String dataspaceName, Collection<String> schemaSetNames);
+
     /**
      * Read all anchors in the given a dataspace.
      *
@@ -116,4 +125,12 @@ public interface CpsAdminPersistenceService {
      * @param anchorName anchor name
      */
     void deleteAnchor(String dataspaceName, String anchorName);
+
+    /**
+     * Delete anchors by name in given dataspace.
+     *
+     * @param dataspaceName dataspace name
+     * @param anchorNames   anchor names
+     */
+    void deleteAnchors(String dataspaceName, Collection<String> anchorNames);
 }
index 3e0b447..90e6ec7 100644 (file)
@@ -3,7 +3,7 @@
  *  Copyright (C) 2020-2023 Nordix Foundation.
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2022 Bell Canada
- *  Modifications Copyright (C) 2022 TechMahindra Ltd.
+ *  Modifications Copyright (C) 2022-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.
@@ -99,30 +99,33 @@ public interface CpsDataPersistenceService {
             Collection<Collection<DataNode>> newLists);
 
     /**
-     * Retrieves datanode by XPath for given dataspace and anchor.
+     * Retrieves multiple datanodes for a single XPath for given dataspace and anchor.
+     * Multiple data nodes are returned when xPath is set to root '/', otherwise single data node
+     * is returned when a specific xpath is used (Example: /bookstore).
      *
      * @param dataspaceName          dataspace name
      * @param anchorName             anchor name
-     * @param xpath                  xpath
+     * @param xpath                  one xpath
      * @param fetchDescendantsOption defines the scope of data to fetch: either single node or all the descendant nodes
      *                               (recursively) as well
-     * @return data node object
+     * @return collection of data node object
      */
-    DataNode getDataNode(String dataspaceName, String anchorName, String xpath,
-        FetchDescendantsOption fetchDescendantsOption);
+    Collection<DataNode> getDataNodes(String dataspaceName, String anchorName, String xpath,
+                                      FetchDescendantsOption fetchDescendantsOption);
 
     /**
-     * Retrieves datanode by XPath for given dataspace and anchor.
+     * Retrieves multiple datanodes for multiple XPaths, given a dataspace and anchor.
      *
-     * @param dataspaceName          dataspace name
-     * @param anchorName             anchor name
-     * @param xpaths                 collection of xpaths
-     * @param fetchDescendantsOption defines the scope of data to fetch: either single node or all the descendant nodes
-     *                               (recursively) as well
-     * @return data node object
+     * @param dataspaceName           dataspace name
+     * @param anchorName              anchor name
+     * @param xpaths                  collection of xpaths
+     * @param fetchDescendantsOption  defines the scope of data to fetch: either single node or all the descendant nodes
+     *                                (recursively) as well
+     * @return collection of data node object
      */
-    Collection<DataNode> getDataNodes(String dataspaceName, String anchorName, Collection<String> xpaths,
-                         FetchDescendantsOption fetchDescendantsOption);
+    Collection<DataNode> getDataNodesForMultipleXpaths(String dataspaceName, String anchorName,
+                                                       Collection<String> xpaths,
+                                                       FetchDescendantsOption fetchDescendantsOption);
 
     /**
      * Updates leaves for existing data node.
@@ -190,6 +193,14 @@ public interface CpsDataPersistenceService {
      */
     void deleteDataNodes(String dataspaceName, String anchorName);
 
+    /**
+     * Deletes all dataNodes in multiple anchors.
+     *
+     * @param dataspaceName   dataspace name
+     * @param anchorNames     anchor names
+     */
+    void deleteDataNodes(String dataspaceName, Collection<String> anchorNames);
+
     /**
      * Deletes a single existing list element or the whole list.
      *
index 0c8cddc..cf5e04d 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2021 Pantheon.tech
- *  Copyright (C) 2022 Nordix Foundation
+ *  Copyright (C) 2022-2023 Nordix Foundation
  *  Modifications Copyright (C) 2023 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -30,15 +30,24 @@ import org.onap.cps.spi.exceptions.DataValidationException;
 @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);
+    public static final FetchDescendantsOption DIRECT_CHILDREN_ONLY
+        = new FetchDescendantsOption(1, "DirectChildrenOnly");
+    public static final FetchDescendantsOption OMIT_DESCENDANTS
+        = new FetchDescendantsOption(0, "OmitDescendants");
+    public static final FetchDescendantsOption INCLUDE_ALL_DESCENDANTS
+        = new FetchDescendantsOption(-1, "IncludeAllDescendants");
+
+    FetchDescendantsOption(final int depth) {
+        this(depth, "Depth=" + depth);
+    }
 
     private static final Pattern FETCH_DESCENDANTS_OPTION_PATTERN =
         Pattern.compile("^$|^all$|^none$|^[0-9]+$|^-1$");
 
     private final int depth;
 
+    private final String optionName;
+
     /**
      * Has next depth.
      *
@@ -85,6 +94,11 @@ public class FetchDescendantsOption {
         }
     }
 
+    @Override
+    public String toString() {
+        return optionName;
+    }
+
     private static void validateFetchDescendantsOption(final String fetchDescendantsOptionAsString) {
         if (Strings.isNullOrEmpty(fetchDescendantsOptionAsString)) {
             return;
diff --git a/cps-service/src/main/java/org/onap/cps/spi/exceptions/DataNodeNotFoundExceptionBatch.java b/cps-service/src/main/java/org/onap/cps/spi/exceptions/DataNodeNotFoundExceptionBatch.java
new file mode 100644 (file)
index 0000000..f38c41b
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2023 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.exceptions;
+
+import java.util.Collection;
+import lombok.Getter;
+
+@SuppressWarnings("squid:S110")  // Team agreed to accept 6 levels of inheritance for CPS Exceptions
+public class DataNodeNotFoundExceptionBatch extends DataNodeNotFoundException {
+
+    @Getter
+    private final Collection<String> notFoundXpaths;
+
+    public DataNodeNotFoundExceptionBatch(final String dataspaceName, final String anchorName,
+                                          final Collection<String> notFoundXpaths) {
+        super(dataspaceName, anchorName);
+        this.notFoundXpaths = notFoundXpaths;
+    }
+
+}
index e7d4e4d..4e0349d 100755 (executable)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2020-2022 Nordix Foundation
+ *  Copyright (C) 2020-2023 Nordix Foundation
  *  Modifications Copyright (C) 2020-2022 Bell Canada.
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2022 TechMahindra Ltd.
@@ -79,6 +79,19 @@ class CpsAdminServiceImplSpec extends Specification {
             1 * mockCpsValidator.validateNameCharacters('someDataspace', 'someSchemaSet')
     }
 
+    def 'Retrieve all anchors for multiple schema-sets.'() {
+        given: 'that anchor is associated with the dataspace and schemasets'
+            def anchors = [new Anchor(), new Anchor()]
+            mockCpsAdminPersistenceService.getAnchors('someDataspace', _ as Collection<String>) >> anchors
+        when: 'get anchors is called for a dataspace name and schema set names'
+            def result = objectUnderTest.getAnchors('someDataspace', ['schemaSet1', 'schemaSet2'])
+        then: 'the collection provided by persistence service is returned as result'
+            result == anchors
+        and: 'the CpsValidator is called on the dataspace name and schema-set names'
+            1 * mockCpsValidator.validateNameCharacters('someDataspace')
+            1 * mockCpsValidator.validateNameCharacters(_)
+    }
+
     def 'Retrieve anchor for dataspace and provided anchor name.'() {
         given: 'that anchor name is associated with the dataspace'
             Anchor anchor = new Anchor()
@@ -118,6 +131,18 @@ class CpsAdminServiceImplSpec extends Specification {
             1 * mockCpsValidator.validateNameCharacters('someDataspace', 'someAnchor')
     }
 
+    def 'Delete multiple anchors.'() {
+        when: 'delete anchors is invoked'
+            objectUnderTest.deleteAnchors('someDataspace', ['anchor1', 'anchor2'])
+        then: 'delete data nodes is invoked on the data service with expected parameters'
+            1 * mockCpsDataService.deleteDataNodes('someDataspace', _ as Collection<String>, _ as OffsetDateTime)
+        and: 'the persistence service method is invoked with same parameters to delete anchor'
+            1 * mockCpsAdminPersistenceService.deleteAnchors('someDataspace',_ as Collection<String>)
+        and: 'the CpsValidator is called on the dataspace name and anchor names'
+            1 * mockCpsValidator.validateNameCharacters('someDataspace')
+            1 * mockCpsValidator.validateNameCharacters(_)
+    }
+
     def 'Query all anchor identifiers for a dataspace and module names.'() {
         given: 'the persistence service is invoked with the expected parameters and returns a list of anchors'
             mockCpsAdminPersistenceService.queryAnchors('some-dataspace-name', ['some-module-name']) >> [new Anchor(name:'some-anchor-identifier')]
index 8bbf4e5..e304d28 100644 (file)
@@ -3,7 +3,7 @@
  *  Copyright (C) 2021-2023 Nordix Foundation
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2021-2022 Bell Canada.
- *  Modifications Copyright (C) 2022 TechMahindra Ltd.
+ *  Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
  *  Modifications Copyright (C) 2022 Deutsche Telekom AG
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -184,13 +184,27 @@ class CpsDataServiceImplSpec extends Specification {
             thrown(DataValidationException)
     }
 
-    def 'Get data node with option #fetchDescendantsOption.'() {
-        def xpath = '/xpath'
-        def dataNode = new DataNodeBuilder().withXpath(xpath).build()
+    def 'Get all data nodes #scenario.'() {
+        given: 'persistence service returns data for GET request'
+            mockCpsDataPersistenceService.getDataNodes(dataspaceName, anchorName, xpath, fetchDescendantsOption) >> dataNode
+        expect: 'service returns same data if using same parameters'
+            objectUnderTest.getDataNodes(dataspaceName, anchorName, xpath, fetchDescendantsOption) == dataNode
+        where: 'following parameters were used'
+            scenario                                   | xpath   | fetchDescendantsOption                         |   dataNode
+            'with root node xpath and descendants'     | '/'     | FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS | [new DataNodeBuilder().withXpath('/xpath-1').build(), new DataNodeBuilder().withXpath('/xpath-2').build()]
+            'with root node xpath and no descendants'  | '/'     | FetchDescendantsOption.OMIT_DESCENDANTS        | [new DataNodeBuilder().withXpath('/xpath-1').build(), new DataNodeBuilder().withXpath('/xpath-2').build()]
+            'with valid xpath and descendants'         | '/xpath'| FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS | [new DataNodeBuilder().withXpath('/xpath').build()]
+            'with valid xpath and no descendants'      | '/xpath'| FetchDescendantsOption.OMIT_DESCENDANTS        | [new DataNodeBuilder().withXpath('/xpath').build()]
+    }
+
+    def 'Get all data nodes over multiple xpaths with option #fetchDescendantsOption.'() {
+        def xpath1 = '/xpath-1'
+        def xpath2 = '/xpath-2'
+        def dataNode = [new DataNodeBuilder().withXpath(xpath1).build(), new DataNodeBuilder().withXpath(xpath2).build()]
         given: 'persistence service returns data for get data request'
-            mockCpsDataPersistenceService.getDataNode(dataspaceName, anchorName, xpath, fetchDescendantsOption) >> dataNode
+            mockCpsDataPersistenceService.getDataNodesForMultipleXpaths(dataspaceName, anchorName, [xpath1, xpath2], fetchDescendantsOption) >> dataNode
         expect: 'service returns same data if uses same parameters'
-            objectUnderTest.getDataNode(dataspaceName, anchorName, xpath, fetchDescendantsOption) == dataNode
+            objectUnderTest.getDataNodesForMultipleXpaths(dataspaceName, anchorName, [xpath1, xpath2], fetchDescendantsOption) == dataNode
         where: 'all fetch options are supported'
             fetchDescendantsOption << [FetchDescendantsOption.OMIT_DESCENDANTS, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS]
     }
@@ -364,6 +378,19 @@ class CpsDataServiceImplSpec extends Specification {
             1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName)
     }
 
+    def 'Delete all data nodes for given dataspace and multiple anchors.'() {
+        given: 'schema set for given anchors and dataspace references test tree model'
+            setupSchemaSetMocks('test-tree.yang')
+        when: 'delete data node method is invoked with correct parameters'
+            objectUnderTest.deleteDataNodes(dataspaceName, ['anchor1', 'anchor2'], observedTimestamp)
+        then: 'data updated events are sent to notification service before the delete'
+            2 * mockNotificationService.processDataUpdatedEvent(dataspaceName, _, '/', Operation.DELETE, observedTimestamp)
+        and: 'the CpsValidator is called on the dataspace name and the anchor names'
+            2 * mockCpsValidator.validateNameCharacters(_)
+        and: 'the persistence service method is invoked with the correct parameters'
+            1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, _ as Collection<String>)
+    }
+
     def setupSchemaSetMocks(String... yangResources) {
         def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet)
         mockYangTextSchemaSourceSetCache.get(dataspaceName, schemaSetName) >> mockYangTextSchemaSourceSet
@@ -404,4 +431,4 @@ class CpsDataServiceImplSpec extends Specification {
             1 * mockCpsDataPersistenceService.lockAnchor('some-sessionId', 'some-dataspaceName',
                     'some-anchorName', 250L)
     }
-}
+}
\ No newline at end of file
index 615d3af..3884eda 100644 (file)
@@ -169,12 +169,11 @@ class CpsModuleServiceImplSpec extends Specification {
 
     def 'Delete multiple schema-sets when cascade is allowed.'() {
         given: '#numberOfAnchors anchors are associated with each schemaset'
-            mockCpsAdminService.getAnchors('my-dataspace', 'my-schemaset1') >> createAnchors(numberOfAnchors)
-            mockCpsAdminService.getAnchors('my-dataspace', 'my-schemaset2') >> createAnchors(numberOfAnchors)
+            mockCpsAdminService.getAnchors('my-dataspace', ['my-schemaset1', 'my-schemaset2']) >> createAnchors(numberOfAnchors * 2)
         when: 'schema set deletion is requested with cascade allowed'
             objectUnderTest.deleteSchemaSetsWithCascade('my-dataspace', ['my-schemaset1', 'my-schemaset2'])
-        then: 'anchor deletion is called 2 * #numberOfAnchors times'
-            (2 * numberOfAnchors) * mockCpsAdminService.deleteAnchor('my-dataspace', _)
+        then: 'anchor deletion is called #numberOfAnchors times'
+            mockCpsAdminService.deleteAnchors('my-dataspace', _)
         and: 'persistence service method is invoked with same parameters'
             mockCpsModulePersistenceService.deleteSchemaSets('my-dataspace', _)
         and: 'schema sets will be removed from the cache'
index 60286b6..56c43d1 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2022 Nordix Foundation
+ *  Copyright (C) 2021-2023 Nordix Foundation
  *  Modifications Copyright (C) 2023 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -45,7 +45,7 @@ class CpsQueryServiceImplSpec extends Specification {
             1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName)
         where: 'all fetch descendants options are supported'
             fetchDescendantsOption << [FetchDescendantsOption.OMIT_DESCENDANTS, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS,
-                FetchDescendantsOption.FETCH_DIRECT_CHILDREN_ONLY, new FetchDescendantsOption(10)]
+                                       FetchDescendantsOption.DIRECT_CHILDREN_ONLY, new FetchDescendantsOption(10)]
     }
 
 }
index 6f9a148..5dbc2bb 100644 (file)
@@ -2,6 +2,7 @@
  *  ============LICENSE_START=======================================================
  *  Copyright (c) 2021-2022 Bell Canada.
  *  Modifications Copyright (c) 2022 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.
@@ -50,8 +51,8 @@ class CpsDataUpdateEventFactorySpec extends Specification {
         and: 'cps data service returns the data node details'
             def xpath = '/xpath'
             def dataNode = new DataNodeBuilder().withXpath(xpath).withLeaves(['leafName': 'leafValue']).build()
-            mockCpsDataService.getDataNode(
-                    'my-dataspace', 'my-anchorname', '/', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
+            mockCpsDataService.getDataNodes(
+                    'my-dataspace', 'my-anchorname', '/', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> [dataNode]
         when: 'CPS data updated event is created'
             def cpsDataUpdatedEvent = objectUnderTest.createCpsDataUpdatedEvent(anchor,
                     DateTimeUtility.toOffsetDateTime(inputObservedTimestamp), Operation.CREATE)
index c4d3dd8..24f3487 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2022 Nordix Foundation
+ *  Copyright (C) 2022-2023 Nordix Foundation
  *  Modifications Copyright (C) 2023 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
 
 package org.onap.cps.spi
 
-import org.onap.cps.spi.exceptions.DataValidationException
 import spock.lang.Specification
 
 class FetchDescendantsOptionSpec extends Specification {
-    def 'Check has next descendant for fetch descendant option: #scenario'() {
+
+    def '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
+            assert fetchDescendantsOption.hasNext() == expectedHasNext
         where: 'following parameters are used'
             scenario                  | depth || expectedHasNext
             'omit descendants'        | 0     || false
@@ -38,7 +38,7 @@ class FetchDescendantsOptionSpec extends Specification {
             'include all descendants' | -1    || true
     }
 
-    def 'Check has next descendant for fetch descendant option: invalid depth'() {
+    def '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'
@@ -47,7 +47,7 @@ class FetchDescendantsOptionSpec extends Specification {
             thrown IllegalArgumentException
     }
 
-    def 'Get next descendant for fetch descendant option: #scenario'() {
+    def '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'
@@ -58,14 +58,14 @@ class FetchDescendantsOptionSpec extends Specification {
             'second child'            | 2
     }
 
-    def 'Get next descendant for fetch descendant option: include all descendants'() {
+    def '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'() {
+    def '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'
@@ -74,7 +74,7 @@ class FetchDescendantsOptionSpec extends Specification {
             thrown IllegalArgumentException
     }
 
-    def 'Create fetch descendant option with  descendant using #scenario'() {
+    def 'Create fetch descendant option with  descendant using #scenario.'() {
         when: 'the next level of depth is not allowed'
            def FetchDescendantsOption fetchDescendantsOption = FetchDescendantsOption.getFetchDescendantsOption(fetchDescendantsOptionAsString)
         then: 'fetch descendant object created'
@@ -87,4 +87,16 @@ class FetchDescendantsOptionSpec extends Specification {
             'No descendants using none'         | 'none'                         || 0
             'til 10th descendants using number' | '10'                           || 10
     }
+
+    def 'String values.'() {
+        expect: 'each fetch descendant option has the correct String value'
+            assert fetchDescendantsOption.toString() == expectedStringValue
+        where: 'the following option is used'
+            fetchDescendantsOption                         || expectedStringValue
+            FetchDescendantsOption.OMIT_DESCENDANTS        || 'OmitDescendants'
+            FetchDescendantsOption.DIRECT_CHILDREN_ONLY    || 'DirectChildrenOnly'
+            FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS || 'IncludeAllDescendants'
+            new FetchDescendantsOption(2)                  || 'Depth=2'
+    }
+
 }
index 9bed964..9c5b796 100755 (executable)
@@ -16,6 +16,31 @@ CPS Release Notes
 ..      * * *   LONDON   * * *
 ..      ======================
 
+Version: 3.2.4
+==============
+
+Release Data
+------------
+
++--------------------------------------+--------------------------------------------------------+
+| **CPS Project**                      |                                                        |
+|                                      |                                                        |
++--------------------------------------+--------------------------------------------------------+
+| **Docker images**                    | onap/cps-and-ncmp:3.2.4                                |
+|                                      |                                                        |
++--------------------------------------+--------------------------------------------------------+
+| **Release designation**              | 3.2.4 London                                           |
+|                                      |                                                        |
++--------------------------------------+--------------------------------------------------------+
+| **Release date**                     | Not been released yet                                  |
+|                                      |                                                        |
++--------------------------------------+--------------------------------------------------------+
+
+Bug Fixes
+---------
+3.2.4
+   - None
+
 Version: 3.2.3
 ==============
 
@@ -32,18 +57,19 @@ Release Data
 | **Release designation**              | 3.2.3 London                                           |
 |                                      |                                                        |
 +--------------------------------------+--------------------------------------------------------+
-| **Release date**                     | Not been released yet                                  |
+| **Release date**                     | 2023 March 07                                          |
 |                                      |                                                        |
 +--------------------------------------+--------------------------------------------------------+
 
 Bug Fixes
 ---------
 3.2.3
-   - None
+   - `CPS-1494 <https://jira.onap.org/browse/CPS-1494>`_ NCMP Inventory Performance Improvements
 
 Features
 --------
-   - None
+    - `CPS-1401 <https://jira.onap.org/browse/CPS-1401>`_ Added V2 of Get Data Node API,support to retrieve all data nodes under an anchor
+    - `CPS-1502 <https://jira.onap.org/browse/CPS-1502>`_ Delete Performance Improvements
 
 Version: 3.2.2
 ==============
index 1351ff9..f2f5cb3 100644 (file)
@@ -23,7 +23,7 @@
     <parent>
         <groupId>org.onap.cps</groupId>
         <artifactId>cps-parent</artifactId>
-        <version>3.2.3-SNAPSHOT</version>
+        <version>3.2.4-SNAPSHOT</version>
         <relativePath>../cps-parent/pom.xml</relativePath>
     </parent>
 
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/CpsPersistenceSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/CpsPersistenceSpec.groovy
deleted file mode 100644 (file)
index 94bcb0a..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2023 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.integration
-
-import org.onap.cps.spi.FetchDescendantsOption
-
-class CpsPersistenceSpec extends CpsIntegrationSpecBase{
-
-    def 'Test creation of test data'() {
-        when: 'A dataspace, schema set and anchor are persisted'
-            createDataspaceSchemaSetAnchor(TEST_DATASPACE, BOOKSTORE_SCHEMA_SET, 'bookstore.yang', TEST_ANCHOR)
-        and: 'data nodes are persisted under the created anchor'
-            saveDataNodes(TEST_DATASPACE, TEST_ANCHOR, '/', 'BookstoreDataNodes.json')
-        then: 'The dataspace has been persisted successfully'
-            cpsAdminService.getDataspace(TEST_DATASPACE).getName() == TEST_DATASPACE
-        and: 'The schema set has been persisted successfully'
-            cpsModuleService.getSchemaSet(TEST_DATASPACE, BOOKSTORE_SCHEMA_SET).getName() == BOOKSTORE_SCHEMA_SET
-        and: 'The anchor has been persisted successfully'
-            cpsAdminService.getAnchor(TEST_DATASPACE, TEST_ANCHOR).getName() == TEST_ANCHOR
-        and: 'The data nodes have been persisted successfully'
-            cpsDataService.getDataNode(TEST_DATASPACE, TEST_ANCHOR, '/bookstore', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS).xpath == '/bookstore'
-    }
-
-    def 'Test deletion of all test data'() {
-        when: 'delete all from test dataspace method is called'
-            deleteAllFromTestDataspace()
-        and: 'the test dataspace is deleted'
-            cpsAdminService.deleteDataspace(TEST_DATASPACE)
-        then: 'there is no test dataspace'
-            !cpsAdminService.getAllDataspaces().contains(TEST_DATASPACE)
-    }
-
-    def 'Read test for persisted data nodes'() {
-        given:'There is a test dataspace created'
-            cpsAdminService.createDataspace(TEST_DATASPACE)
-        and: 'There is a schema set and anchor for the test dataspace'
-            createSchemaSetAnchor(TEST_DATASPACE, 'bookstoreSchemaSet', 'bookstore.yang', TEST_ANCHOR)
-        when: 'data is persisted to the database'
-            saveDataNodes(TEST_DATASPACE, TEST_ANCHOR, "/", "BookstoreDataNodes.json")
-        then: 'the correct data is saved'
-            cpsDataService.getDataNode(TEST_DATASPACE, TEST_ANCHOR, '/bookstore', FetchDescendantsOption.OMIT_DESCENDANTS).leaves['bookstore-name'] == 'Easons'
-    }
-}
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/BookstoreSpecBase.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/BookstoreSpecBase.groovy
new file mode 100644 (file)
index 0000000..7eb47b3
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2023 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.integration.base
+
+import java.time.OffsetDateTime
+
+class BookstoreSpecBase extends CpsIntegrationSpecBase {
+
+    def static initialized = false
+
+    def setup() {
+        if (!initialized) {
+            setupBookstoreInfraStructure()
+            addBookstoreData()
+            initialized = true
+        }
+    }
+
+    def setupBookstoreInfraStructure() {
+        cpsAdminService.createDataspace(BOOKSTORE_DATASPACE)
+        def bookstoreYangModelAsString = readResourceFile('bookstore.yang')
+        cpsModuleService.createSchemaSet(BOOKSTORE_DATASPACE, BOOKSTORE_SCHEMA_SET, [bookstore : bookstoreYangModelAsString])
+        cpsAdminService.createAnchor(BOOKSTORE_DATASPACE, BOOKSTORE_SCHEMA_SET, BOOKSTORE_ANCHOR)
+    }
+
+    def addBookstoreData() {
+        def bookstoreJsonData = readResourceFile('BookstoreDataNodes.json')
+        cpsDataService.saveData(BOOKSTORE_DATASPACE, BOOKSTORE_ANCHOR, bookstoreJsonData, OffsetDateTime.now())
+    }
+
+}
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.integration
+package org.onap.cps.integration.base
 
 import org.onap.cps.api.impl.CpsAdminServiceImpl
 import org.onap.cps.api.impl.CpsDataServiceImpl
 import org.onap.cps.api.impl.CpsModuleServiceImpl
-import org.onap.cps.spi.CascadeDeleteAllowed
+import org.onap.cps.integration.DatabaseTestContainer
+import org.onap.cps.spi.model.DataNode
 import org.onap.cps.spi.repository.DataspaceRepository
 import org.onap.cps.spi.impl.utils.CpsValidatorImpl
-import org.onap.cps.utils.ContentType
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.boot.autoconfigure.EnableAutoConfiguration
 import org.springframework.boot.autoconfigure.domain.EntityScan
@@ -38,8 +38,6 @@ import org.testcontainers.spock.Testcontainers
 import spock.lang.Shared
 import spock.lang.Specification
 
-import java.time.OffsetDateTime
-
 @SpringBootTest(classes = [TestConfig, CpsAdminServiceImpl, CpsValidatorImpl])
 @Testcontainers
 @EnableAutoConfiguration
@@ -63,50 +61,35 @@ class CpsIntegrationSpecBase extends Specification {
     @Lazy
     CpsModuleServiceImpl cpsModuleService
 
-
-    def static TEST_DATASPACE = 'testDataspace'
+    def static GENERAL_TEST_DATASPACE = 'generalTestDataSpace'
+    def static BOOKSTORE_DATASPACE = 'bookstoreDataspace'
     def static BOOKSTORE_SCHEMA_SET = 'bookstoreSchemaSet'
-    def static TEST_ANCHOR = 'testAnchor'
+    def static BOOKSTORE_ANCHOR = 'bookstoreAnchor'
 
-    def createDataspaceSchemaSetAnchor(String dataspaceName, String schemaSetName, String schemaSetFileName, String anchorName) {
-        cpsAdminService.createDataspace(dataspaceName)
-        createSchemaSetAnchor(dataspaceName, schemaSetName, schemaSetFileName, anchorName)
-    }
+    def static initialized = false
 
-    def createSchemaSetAnchor(String dataspaceName, String schemaSetName, String schemaSetFileName, String anchorName) {
-        def bookstoreFileContent = readResourceFile(schemaSetFileName)
-        cpsModuleService.createSchemaSet(dataspaceName, schemaSetName, [(schemaSetFileName) : bookstoreFileContent])
-        cpsAdminService.createAnchor(dataspaceName, schemaSetName, anchorName)
+    def setup() {
+        if (!initialized) {
+            cpsAdminService.createDataspace(GENERAL_TEST_DATASPACE)
+            def bookstoreModelFileContent = readResourceFile('bookstore.yang')
+            cpsModuleService.createSchemaSet(GENERAL_TEST_DATASPACE, BOOKSTORE_SCHEMA_SET, [bookstore : bookstoreModelFileContent])
+            initialized = true;
+        }
     }
 
-    def saveDataNodes(String dataspaceName, String anchorName, String parentNodeXpath, String dataNodesFileName) {
-        def dataNodesAsJSON = readResourceFile(dataNodesFileName)
-        if (isRootXpath(parentNodeXpath)) {
-            cpsDataService.saveData(dataspaceName, anchorName, dataNodesAsJSON,
-                OffsetDateTime.now(), ContentType.JSON);
-        } else {
-            cpsDataService.saveData(dataspaceName, anchorName, parentNodeXpath,
-                dataNodesAsJSON, OffsetDateTime.now(), ContentType.JSON);
-        }
+    def static countDataNodesInTree(DataNode dataNode) {
+        return 1 + countDataNodesInTree(dataNode.getChildDataNodes())
     }
 
-    def deleteAllFromTestDataspace() {
-        def anchors = cpsAdminService.getAnchors(TEST_DATASPACE)
-        for(anchor in anchors) {
-            cpsDataService.deleteDataNodes(TEST_DATASPACE, anchor.getName(), OffsetDateTime.now())
-            cpsAdminService.deleteAnchor(TEST_DATASPACE, anchor.getName())
-        }
-        def schemaSets = cpsModuleService.getSchemaSets(TEST_DATASPACE)
-        for(schemaSet in schemaSets) {
-            cpsModuleService.deleteSchemaSet(TEST_DATASPACE, schemaSet.getName(), CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED)
+    def static countDataNodesInTree(Collection<DataNode> dataNodes) {
+        int nodeCount = 0
+        for (DataNode parent : dataNodes) {
+            nodeCount += countDataNodesInTree(parent)
         }
+        return nodeCount
     }
 
-    def static readResourceFile(String filename) {
+    def static readResourceFile(filename) {
         return new File('src/test/resources/data/' + filename).text
     }
-
-    def static isRootXpath(final String xpath) {
-        return "/".equals(xpath);
-    }
 }
@@ -18,7 +18,7 @@
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.integration
+package org.onap.cps.integration.base
 
 import com.fasterxml.jackson.databind.ObjectMapper
 import org.onap.cps.notification.NotificationService
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsAdminServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsAdminServiceIntegrationSpec.groovy
new file mode 100644 (file)
index 0000000..d504a9e
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2023 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.integration.functional
+
+import org.onap.cps.integration.base.CpsIntegrationSpecBase
+import org.onap.cps.spi.exceptions.AlreadyDefinedException
+import org.onap.cps.spi.exceptions.AnchorNotFoundException
+import org.onap.cps.spi.exceptions.DataspaceNotFoundException
+
+class CpsAdminServiceIntegrationSpec extends CpsIntegrationSpecBase {
+
+    def objectUnderTest
+
+    def setup() { objectUnderTest = cpsAdminService }
+
+    def 'Dataspace CRUD operations.'() {
+        when: 'a dataspace is created'
+            objectUnderTest.createDataspace('newDataspace')
+        then: 'the dataspace can be read'
+            assert objectUnderTest.getDataspace('newDataspace').name == 'newDataspace'
+        and: 'it can be deleted'
+            objectUnderTest.deleteDataspace('newDataspace')
+        then: 'the dataspace no longer exists i.e. an exception is thrown if an attempt is made to retrieve it'
+            def thrown = null
+            try {
+                objectUnderTest.getDataspace('newDataspace')
+            } catch(Exception e) {
+                thrown = e
+            }
+           assert thrown instanceof DataspaceNotFoundException
+    }
+
+    def 'Retrieve all dataspaces (depends on total test suite).'() {
+        given: 'two addtional dataspaces are created'
+            objectUnderTest.createDataspace('dataspace1')
+            objectUnderTest.createDataspace('dataspace2')
+        when: 'all datespaces are retreived'
+            def result = objectUnderTest.getAllDataspaces()
+        then: 'there are at least 3 dataspaces (2 new ones plus the general test dataspace)'
+            result.size() >= 3
+            assert result.name.containsAll([GENERAL_TEST_DATASPACE, 'dataspace1', 'dataspace2'])
+    }
+
+    def 'Duplicate dataspaces.'() {
+        when: 'attempting to create a dataspace with the same name as an existing one'
+            objectUnderTest.createDataspace(GENERAL_TEST_DATASPACE)
+        then: 'an exception is thrown indicating the dataspace already exists'
+            thrown(AlreadyDefinedException)
+    }
+
+    def 'Anchor CRUD operations.'() {
+        when: 'a anchor is created'
+            objectUnderTest.createAnchor(GENERAL_TEST_DATASPACE, BOOKSTORE_SCHEMA_SET, 'newAnchor')
+        then: 'the anchor be read'
+            assert objectUnderTest.getAnchor(GENERAL_TEST_DATASPACE, 'newAnchor').name == 'newAnchor'
+        and: 'it can be deleted'
+            objectUnderTest.deleteAnchor(GENERAL_TEST_DATASPACE,'newAnchor')
+        then: 'the anchor no longer exists i.e. an exception is thrown if an attempt is made to retrieve it'
+            def thrown = null
+            try {
+                objectUnderTest.getAnchor(GENERAL_TEST_DATASPACE, 'newAnchor')
+            } catch(Exception e) {
+                thrown = e
+            }
+            assert thrown instanceof AnchorNotFoundException
+    }
+
+    def 'Filtering multiple anchors.'() {
+        when: '2 anchors with bookstore schema set are created'
+            objectUnderTest.createAnchor(GENERAL_TEST_DATASPACE, BOOKSTORE_SCHEMA_SET, 'anchor1')
+            objectUnderTest.createAnchor(GENERAL_TEST_DATASPACE, BOOKSTORE_SCHEMA_SET, 'anchor2')
+        and: '1 anchor with "other" schema set is created'
+            def bookstoreModelFileContent = readResourceFile('bookstore.yang')
+            cpsModuleService.createSchemaSet(GENERAL_TEST_DATASPACE, 'otherSchemaSet', [someFileName: bookstoreModelFileContent])
+            objectUnderTest.createAnchor(GENERAL_TEST_DATASPACE, 'otherSchemaSet', 'anchor3')
+        then: 'there are 3 anchors in the general test database'
+            assert objectUnderTest.getAnchors(GENERAL_TEST_DATASPACE).size() == 3
+        and: 'there are 2 anchors associated with bookstore schema set'
+            assert objectUnderTest.getAnchors(GENERAL_TEST_DATASPACE, BOOKSTORE_SCHEMA_SET).size() == 2
+        and: 'there is 1 anchor associated with other schema set'
+            assert objectUnderTest.getAnchors(GENERAL_TEST_DATASPACE, 'otherSchemaSet').size() == 1
+    }
+
+    def 'Querying anchor(name)s (depends on previous test!).'() {
+        expect: 'there are now 3 anchors using the "stores" module (both schema sets use the same modules) '
+            assert objectUnderTest.queryAnchorNames(GENERAL_TEST_DATASPACE, ['stores']).size() == 3
+        and: 'there are no anchors using both "stores" and a "unused-model"'
+            assert objectUnderTest.queryAnchorNames(GENERAL_TEST_DATASPACE, ['stores', 'unused-model']).size() == 0
+    }
+
+}
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy
new file mode 100644 (file)
index 0000000..5e839f2
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 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.integration.functional
+
+import org.onap.cps.integration.base.BookstoreSpecBase
+import org.onap.cps.spi.FetchDescendantsOption
+
+class CpsDataServiceIntegrationSpec extends BookstoreSpecBase {
+
+    def objectUnderTest
+
+    def setup() { objectUnderTest = cpsDataService }
+
+    def 'Read bookstore top-level container(s) using #fetchDescendantsOption.'() {
+        when: 'get data nodes for bookstore container'
+            def result = objectUnderTest.getDataNodes(BOOKSTORE_DATASPACE, BOOKSTORE_ANCHOR, '/bookstore', fetchDescendantsOption)
+        then: 'the tree consist ouf of #expectNumberOfDataNodes data nodes'
+            assert countDataNodesInTree(result) == expectNumberOfDataNodes
+        and: 'the top level data node has the expected attribute and value'
+            assert result.leaves['bookstore-name'] == ['Easons']
+        where: 'the following option is used'
+            fetchDescendantsOption                         || expectNumberOfDataNodes
+            FetchDescendantsOption.OMIT_DESCENDANTS        || 1
+            FetchDescendantsOption.DIRECT_CHILDREN_ONLY    || 4
+            FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS || 8
+            new FetchDescendantsOption(2)                  || 8
+    }
+
+}
index fd50df7..b081d52 100644 (file)
@@ -5,7 +5,7 @@
     <parent>
         <groupId>org.onap.cps</groupId>
         <artifactId>cps-parent</artifactId>
-        <version>3.2.3-SNAPSHOT</version>
+        <version>3.2.4-SNAPSHOT</version>
         <relativePath>../cps-parent/pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
diff --git a/pom.xml b/pom.xml
index fd68e82..68ce651 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -32,7 +32,7 @@
 \r
     <groupId>org.onap.cps</groupId>\r
     <artifactId>cps-aggregator</artifactId>\r
-    <version>3.2.3-SNAPSHOT</version>\r
+    <version>3.2.4-SNAPSHOT</version>\r
     <packaging>pom</packaging>\r
 \r
     <name>cps</name>\r
diff --git a/releases/3.2.3-container.yaml b/releases/3.2.3-container.yaml
new file mode 100644 (file)
index 0000000..7504ade
--- /dev/null
@@ -0,0 +1,8 @@
+distribution_type: container
+container_release_tag: 3.2.3
+project: cps
+log_dir: cps-maven-docker-stage-master/873/
+ref: 02bffef6d216ed03206526d49a4fb20124bfafe9
+containers:
+  - name: 'cps-and-ncmp'
+    version: '3.2.3-20230306T164117Z'
diff --git a/releases/3.2.3.yaml b/releases/3.2.3.yaml
new file mode 100644 (file)
index 0000000..62e8144
--- /dev/null
@@ -0,0 +1,4 @@
+distribution_type: maven
+log_dir: cps-maven-stage-master/879/
+project: cps
+version: 3.2.3
\ No newline at end of file
index f622071..e331427 100644 (file)
@@ -25,7 +25,7 @@
     <modelVersion>4.0.0</modelVersion>
     <groupId>org.onap.cps</groupId>
     <artifactId>spotbugs</artifactId>
-    <version>3.2.3-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
 
     <properties>
         <nexusproxy>https://nexus.onap.org</nexusproxy>
index 7c5f030..a620e97 100755 (executable)
@@ -22,7 +22,7 @@
 
 major=3
 minor=2
-patch=3
+patch=4
 
 base_version=${major}.${minor}.${patch}