summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xcps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java9
-rwxr-xr-xcps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryController.java6
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyCmHandleQueryService.java (renamed from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyCmHandlerQueryService.java)12
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java16
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandleQueryServiceImpl.java268
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceImpl.java315
-rwxr-xr-xcps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java31
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java4
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CmHandleQueries.java29
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CmHandleQueriesImpl.java118
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java7
-rw-r--r--cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImpl.java18
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandleQueryServiceSpec.groovy211
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceSpec.groovy235
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy5
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy23
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy12
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CmHandleQueriesImplSpec.groovy54
-rw-r--r--cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImplSpec.groovy16
-rw-r--r--cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java11
-rw-r--r--cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathUtil.java6
-rw-r--r--cps-rest/docs/openapi/cpsDataV2.yml2
-rwxr-xr-xcps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java31
-rw-r--r--cps-rest/src/main/java/org/onap/cps/rest/utils/ZipFileSizeValidator.java6
-rwxr-xr-xcps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy105
-rw-r--r--cps-rest/src/test/groovy/org/onap/cps/rest/utils/MultipartFileUtilSpec.groovy2
-rw-r--r--cps-rest/src/test/groovy/org/onap/cps/rest/utils/ZipFileSizeValidatorSpec.groovy2
-rwxr-xr-xcps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntity.java2
-rwxr-xr-xcps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java16
-rw-r--r--cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java127
-rwxr-xr-xcps-ri/src/main/java/org/onap/cps/spi/repository/AnchorRepository.java11
-rw-r--r--cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentNativeRepositoryImpl.java73
-rwxr-xr-xcps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java6
-rw-r--r--cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy27
-rwxr-xr-xcps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy159
-rw-r--r--cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy61
-rw-r--r--cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistencePerfSpecBase.groovy16
-rw-r--r--cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistenceSpecBase.groovy20
-rw-r--r--cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServiceDeletePerfTest.groovy67
-rw-r--r--cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServicePerfTest.groovy61
-rw-r--r--cps-ri/src/test/resources/data/anchor.sql5
-rwxr-xr-xcps-ri/src/test/resources/data/fragment.sql13
-rwxr-xr-xcps-service/src/main/java/org/onap/cps/api/CpsAdminService.java21
-rw-r--r--cps-service/src/main/java/org/onap/cps/api/CpsDataService.java47
-rwxr-xr-xcps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java17
-rwxr-xr-xcps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java37
-rw-r--r--cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java10
-rw-r--r--cps-service/src/main/java/org/onap/cps/notification/CpsDataUpdatedEventFactory.java5
-rwxr-xr-xcps-service/src/main/java/org/onap/cps/spi/CpsAdminPersistenceService.java19
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java41
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/FetchDescendantsOption.java22
-rw-r--r--cps-service/src/main/java/org/onap/cps/spi/exceptions/DataNodeNotFoundExceptionBatch.java38
-rwxr-xr-xcps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy27
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy41
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy7
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/api/impl/CpsQueryServiceImplSpec.groovy4
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdateEventFactorySpec.groovy5
-rw-r--r--cps-service/src/test/groovy/org/onap/cps/spi/FetchDescendantsOptionSpec.groovy30
-rwxr-xr-xdocs/release-notes.rst2
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/CpsPersistenceSpec.groovy61
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/base/BookstoreSpecBase.groovy49
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy (renamed from integration-test/src/test/groovy/org/onap/cps/integration/CpsIntegrationSpecBase.groovy)61
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/base/TestConfig.groovy (renamed from integration-test/src/test/groovy/org/onap/cps/integration/TestConfig.groovy)2
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsAdminServiceIntegrationSpec.groovy109
-rw-r--r--integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy48
65 files changed, 1688 insertions, 1233 deletions
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java
index 9f171f609..4da251f3c 100755
--- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java
+++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java
@@ -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));
}
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryController.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryController.java
index 0c27d3efb..5d8599a1a 100755
--- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryController.java
+++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryController.java
@@ -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));
}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyCmHandlerQueryService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyCmHandleQueryService.java
index 7322ebc11..91e98e866 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyCmHandlerQueryService.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyCmHandleQueryService.java
@@ -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,18 +20,18 @@
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);
}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java
index c9810e9a6..128eed3f2 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java
@@ -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
index 000000000..54d89ba00
--- /dev/null
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandleQueryServiceImpl.java
@@ -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
index b67ae0c19..000000000
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceImpl.java
+++ /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()));
- }
-}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java
index 508acdc1a..d3a4f530f 100755
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java
@@ -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);
}
}
+
}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java
index f39c2f9b6..bbb2c0f56 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java
@@ -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) {
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CmHandleQueries.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CmHandleQueries.java
index bae0262b0..ff78f0022 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CmHandleQueries.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CmHandleQueries.java
@@ -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,10 +20,9 @@
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);
}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CmHandleQueriesImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CmHandleQueriesImpl.java
index bda0a728b..f61d6c348 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CmHandleQueriesImpl.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CmHandleQueriesImpl.java
@@ -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,22 +21,17 @@
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();
}
}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java
index 73acf4368..cbd30a8fe 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java
@@ -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.
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImpl.java
index 2c978950f..c8d6c05f9 100644
--- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImpl.java
+++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImpl.java
@@ -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
index 000000000..bff822218
--- /dev/null
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandleQueryServiceSpec.groovy
@@ -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
index 05856d0ea..000000000
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceSpec.groovy
+++ /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
- }
-}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy
index 272030b11..f12969def 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy
@@ -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>)
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy
index 578f7f31c..4acd249e6 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy
@@ -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'
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy
index 64461fa7e..0df61f4e0 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy
@@ -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'
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CmHandleQueriesImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CmHandleQueriesImplSpec.groovy
index 2800f5c24..fd01a05ee 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CmHandleQueriesImplSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CmHandleQueriesImplSpec.groovy
@@ -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() {
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImplSpec.groovy
index d01df3abf..efe6f00bc 100644
--- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImplSpec.groovy
+++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImplSpec.groovy
@@ -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'() {
diff --git a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java
index c9df8df90..398545526 100644
--- a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java
+++ b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathQuery.java
@@ -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();
+ }
+
}
diff --git a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathUtil.java b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathUtil.java
index 60f0e2efc..bde9b0638 100644
--- a/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathUtil.java
+++ b/cps-path-parser/src/main/java/org/onap/cps/cpspath/parser/CpsPathUtil.java
@@ -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();
}
/**
diff --git a/cps-rest/docs/openapi/cpsDataV2.yml b/cps-rest/docs/openapi/cpsDataV2.yml
index 61663ab3a..ad0c299d7 100644
--- a/cps-rest/docs/openapi/cpsDataV2.yml
+++ b/cps-rest/docs/openapi/cpsDataV2.yml
@@ -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
diff --git a/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java b/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java
index 3a9c764bc..80cfb8ce0 100755
--- a/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java
+++ b/cps-rest/src/main/java/org/onap/cps/rest/controller/DataRestController.java
@@ -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));
}
}
-
}
diff --git a/cps-rest/src/main/java/org/onap/cps/rest/utils/ZipFileSizeValidator.java b/cps-rest/src/main/java/org/onap/cps/rest/utils/ZipFileSizeValidator.java
index 2e303d1d4..e514b93af 100644
--- a/cps-rest/src/main/java/org/onap/cps/rest/utils/ZipFileSizeValidator.java
+++ b/cps-rest/src/main/java/org/onap/cps/rest/utils/ZipFileSizeValidator.java
@@ -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,
diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
index 16d106ba6..d88a9cdf0 100755
--- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
+++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/DataRestControllerSpec.groovy
@@ -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)
diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/utils/MultipartFileUtilSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/utils/MultipartFileUtilSpec.groovy
index 67ee50e89..572db005b 100644
--- a/cps-rest/src/test/groovy/org/onap/cps/rest/utils/MultipartFileUtilSpec.groovy
+++ b/cps-rest/src/test/groovy/org/onap/cps/rest/utils/MultipartFileUtilSpec.groovy
@@ -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'
diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/utils/ZipFileSizeValidatorSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/utils/ZipFileSizeValidatorSpec.groovy
index 60ecb2e3b..b4a6a6ab1 100644
--- a/cps-rest/src/test/groovy/org/onap/cps/rest/utils/ZipFileSizeValidatorSpec.groovy
+++ b/cps-rest/src/test/groovy/org/onap/cps/rest/utils/ZipFileSizeValidatorSpec.groovy
@@ -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
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntity.java b/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntity.java
index 2ffbb4ae0..82afc5a81 100755
--- a/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntity.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/entities/FragmentEntity.java
@@ -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;
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java
index 2cebfc72c..162b268d8 100755
--- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsAdminPersistenceServiceImpl.java
@@ -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.
@@ -131,6 +131,13 @@ public class CpsAdminPersistenceServiceImpl implements CpsAdminPersistenceServic
}
@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 {
validateDataspaceAndModuleNames(dataspaceName, inputModuleNames);
@@ -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);
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java
index 5b310efd5..f634008dc 100644
--- a/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java
@@ -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);
@@ -607,22 +608,44 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
@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,
final Collection<String> xpathsToDelete) {
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 {
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/AnchorRepository.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/AnchorRepository.java
index 3dbd578c7..46b0fec1c 100755
--- a/cps-ri/src/main/java/org/onap/cps/spi/repository/AnchorRepository.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/AnchorRepository.java
@@ -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);
+}
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentNativeRepositoryImpl.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentNativeRepositoryImpl.java
index 0e4d359da..5c5458a03 100644
--- a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentNativeRepositoryImpl.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentNativeRepositoryImpl.java
@@ -21,8 +21,11 @@
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;
+ }
+
}
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java
index 8bdb7d985..51ebcb412 100755
--- a/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java
+++ b/cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepository.java
@@ -100,4 +100,10 @@ public interface FragmentRepository extends JpaRepository<FragmentEntity, Long>,
nativeQuery = true)
List<FragmentExtract> quickFindWithDescendants(@Param("anchorId") int anchorId,
@Param("xpathRegex") String xpathRegex);
+
+ @Query("SELECT f.xpath FROM FragmentEntity f WHERE f.anchor = :anchor AND f.xpath IN :xpaths")
+ List<String> findAllXpathByAnchorAndXpathIn(@Param("anchor") AnchorEntity anchorEntity,
+ @Param("xpaths") Collection<String> xpaths);
+
+ boolean existsByAnchorAndXpathStartsWith(AnchorEntity anchorEntity, String xpath);
}
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy
index 99d44aac8..28d3bcfa4 100644
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsAdminPersistenceServiceSpec.groovy
@@ -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 || []
}
@@ -179,6 +180,17 @@ class CpsAdminPersistenceServiceSpec extends CpsPersistenceSpecBase {
}
@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'
objectUnderTest.deleteAnchor(DATASPACE_NAME, ANCHOR_NAME2)
@@ -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)'
}
}
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy
index e4c552978..28916b1c4 100755
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy
@@ -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,22 +584,34 @@ 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)'
def grandChild = new DataNodeBuilder().withXpath(deleteTestGrandChildXPath).build()
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() }
}
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy
index 5cabc85b3..ba42a083e 100644
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy
@@ -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]
}
}
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistencePerfSpecBase.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistencePerfSpecBase.groovy
index b67a5cc68..daa774698 100644
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistencePerfSpecBase.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistencePerfSpecBase.groovy
@@ -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
}
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistenceSpecBase.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistenceSpecBase.groovy
index 1ecad4e68..30ff11b45 100644
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistenceSpecBase.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsPersistenceSpecBase.groovy
@@ -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'
}
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServiceDeletePerfTest.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServiceDeletePerfTest.groovy
index 3b9338ce4..eb138b98b 100644
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServiceDeletePerfTest.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServiceDeletePerfTest.groovy
@@ -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)
}
}
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServicePerfTest.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServicePerfTest.groovy
index 0c4f5ec41..3562419c0 100644
--- a/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServicePerfTest.groovy
+++ b/cps-ri/src/test/groovy/org/onap/cps/spi/performance/CpsDataPersistenceServicePerfTest.groovy
@@ -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)
+ }
}
diff --git a/cps-ri/src/test/resources/data/anchor.sql b/cps-ri/src/test/resources/data/anchor.sql
index 40fc44c0a..2ab7966e1 100644
--- a/cps-ri/src/test/resources/data/anchor.sql
+++ b/cps-ri/src/test/resources/data/anchor.sql
@@ -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', '{}');
diff --git a/cps-ri/src/test/resources/data/fragment.sql b/cps-ri/src/test/resources/data/fragment.sql
index ad463cffd..caafcd320 100755
--- a/cps-ri/src/test/resources/data/fragment.sql
+++ b/cps-ri/src/test/resources/data/fragment.sql
@@ -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'),
@@ -69,6 +70,16 @@ INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH, ATTRIBUTES)
(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"}'),
(4203, 1001, 3003, 4202, '/parent-200/child-201/grand-child', '{"leaf-value": "original"}'),
diff --git a/cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java b/cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java
index b0e68cf8f..fcf3f54cc 100755
--- a/cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java
+++ b/cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java
@@ -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
@@ -93,6 +93,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.
*
* @param dataspaceName dataspace name
@@ -110,6 +119,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.
*
* @param dataspaceName dataspace name
diff --git a/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java b/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java
index 174d71f64..39fa45ac1 100644
--- a/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java
+++ b/cps-service/src/main/java/org/onap/cps/api/CpsDataService.java
@@ -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.
@@ -228,6 +230,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.
*
* @param dataspaceName dataspace name
diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java
index ece3eb95c..e286eea17 100755
--- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java
+++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java
@@ -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.
@@ -87,6 +87,13 @@ public class CpsAdminServiceImpl implements CpsAdminService {
}
@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);
return cpsAdminPersistenceService.getAnchor(dataspaceName, anchorName);
@@ -100,6 +107,14 @@ public class CpsAdminServiceImpl implements CpsAdminService {
}
@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);
final Collection<Anchor> anchors = cpsAdminPersistenceService.queryAnchors(dataspaceName, moduleNames);
diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
index 06a084538..721d4a9fb 100755
--- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
+++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
@@ -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);
@@ -282,6 +284,19 @@ public class CpsDataServiceImpl implements CpsDataService {
}
@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")
public void deleteListOrListElement(final String dataspaceName, final String anchorName, final String listNodeXpath,
diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java
index e71e6ce66..d6c01f7a9 100644
--- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java
+++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java
@@ -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) {
diff --git a/cps-service/src/main/java/org/onap/cps/notification/CpsDataUpdatedEventFactory.java b/cps-service/src/main/java/org/onap/cps/notification/CpsDataUpdatedEventFactory.java
index f0cdaee8d..38f898827 100644
--- a/cps-service/src/main/java/org/onap/cps/notification/CpsDataUpdatedEventFactory.java
+++ b/cps-service/src/main/java/org/onap/cps/notification/CpsDataUpdatedEventFactory.java
@@ -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);
}
diff --git a/cps-service/src/main/java/org/onap/cps/spi/CpsAdminPersistenceService.java b/cps-service/src/main/java/org/onap/cps/spi/CpsAdminPersistenceService.java
index 6bcb69844..1c1e80a20 100755
--- a/cps-service/src/main/java/org/onap/cps/spi/CpsAdminPersistenceService.java
+++ b/cps-service/src/main/java/org/onap/cps/spi/CpsAdminPersistenceService.java
@@ -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
@@ -82,6 +82,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.
*
* @param dataspaceName dataspace name
@@ -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);
}
diff --git a/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java b/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java
index 3e0b4475e..90e6ec761 100644
--- a/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java
+++ b/cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java
@@ -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.
@@ -191,6 +194,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.
*
* @param dataspaceName dataspace name
diff --git a/cps-service/src/main/java/org/onap/cps/spi/FetchDescendantsOption.java b/cps-service/src/main/java/org/onap/cps/spi/FetchDescendantsOption.java
index 0c8cddcd7..cf5e04dc4 100644
--- a/cps-service/src/main/java/org/onap/cps/spi/FetchDescendantsOption.java
+++ b/cps-service/src/main/java/org/onap/cps/spi/FetchDescendantsOption.java
@@ -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
index 000000000..f38c41b40
--- /dev/null
+++ b/cps-service/src/main/java/org/onap/cps/spi/exceptions/DataNodeNotFoundExceptionBatch.java
@@ -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;
+ }
+
+}
diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy
index e7d4e4ddb..4e0349d2b 100755
--- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy
@@ -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')]
diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
index 8bbf4e571..e304d28d8 100644
--- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
@@ -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
diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy
index 615d3af35..3884eda66 100644
--- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy
@@ -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'
diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsQueryServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsQueryServiceImplSpec.groovy
index 60286b664..56c43d163 100644
--- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsQueryServiceImplSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsQueryServiceImplSpec.groovy
@@ -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)]
}
}
diff --git a/cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdateEventFactorySpec.groovy b/cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdateEventFactorySpec.groovy
index 6f9a148eb..5dbc2bb04 100644
--- a/cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdateEventFactorySpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdateEventFactorySpec.groovy
@@ -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)
diff --git a/cps-service/src/test/groovy/org/onap/cps/spi/FetchDescendantsOptionSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/spi/FetchDescendantsOptionSpec.groovy
index c4d3dd8b7..24f3487d1 100644
--- a/cps-service/src/test/groovy/org/onap/cps/spi/FetchDescendantsOptionSpec.groovy
+++ b/cps-service/src/test/groovy/org/onap/cps/spi/FetchDescendantsOptionSpec.groovy
@@ -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");
@@ -21,15 +21,15 @@
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'
+ }
+
}
diff --git a/docs/release-notes.rst b/docs/release-notes.rst
index 9bed96428..a29a80923 100755
--- a/docs/release-notes.rst
+++ b/docs/release-notes.rst
@@ -43,7 +43,7 @@ Bug Fixes
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
Version: 3.2.2
==============
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
index 94bcb0a6f..000000000
--- a/integration-test/src/test/groovy/org/onap/cps/integration/CpsPersistenceSpec.groovy
+++ /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
index 000000000..7eb47b35a
--- /dev/null
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/BookstoreSpecBase.groovy
@@ -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())
+ }
+
+}
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/CpsIntegrationSpecBase.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy
index 960483270..567b33cb4 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/CpsIntegrationSpecBase.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy
@@ -18,15 +18,15 @@
* ============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);
- }
}
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/TestConfig.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/TestConfig.groovy
index 0673f7eb4..18a294161 100644
--- a/integration-test/src/test/groovy/org/onap/cps/integration/TestConfig.groovy
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/TestConfig.groovy
@@ -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
index 000000000..d504a9e0d
--- /dev/null
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsAdminServiceIntegrationSpec.groovy
@@ -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
index 000000000..5e839f27a
--- /dev/null
+++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy
@@ -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
+ }
+
+}