Merge "Copyright Check Script"
authorToine Siebelink <toine.siebelink@est.tech>
Wed, 30 Mar 2022 13:17:09 +0000 (13:17 +0000)
committerGerrit Code Review <gerrit@onap.org>
Wed, 30 Mar 2022 13:17:09 +0000 (13:17 +0000)
91 files changed:
checkstyle/pom.xml
cps-application/pom.xml
cps-application/src/main/resources/application.yml
cps-bom/pom.xml
cps-dependencies/pom.xml
cps-events/pom.xml
cps-ncmp-rest/docs/openapi/components.yaml
cps-ncmp-rest/docs/openapi/ncmp.yml
cps-ncmp-rest/pom.xml
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NcmpRestInputMapper.java [moved from cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/RestInputMapper.java with 63% similarity]
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryController.java
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandler.java
cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NcmpRestInputMapperSpec.groovy [moved from cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/RestInputMapperSpec.groovy with 69% similarity]
cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy
cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryControllerSpec.groovy
cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandlerSpec.groovy
cps-ncmp-service/pom.xml
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandler.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/InvalidTopicException.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiOperations.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiRequestBody.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandle.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandlesList.java [deleted file]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponse.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/DmiPluginRegistrationResponse.java [moved from cps-rest/src/test/groovy/org/onap/cps/config/CpsConfigSpec.groovy with 65% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServicePropertyHandlerSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiOperationsBaseSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponseSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/moduleReferenceSpec.groovy [deleted file]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/utils/DmiServiceUrlBuilderSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/resources/application.yml
cps-parent/pom.xml
cps-path-parser/pom.xml
cps-rest/docs/openapi/components.yml
cps-rest/pom.xml
cps-rest/src/main/java/org/onap/cps/config/CpsConfig.java [deleted file]
cps-rest/src/main/java/org/onap/cps/rest/controller/AdminRestController.java
cps-rest/src/main/java/org/onap/cps/rest/controller/CpsRestInputMapper.java [new file with mode: 0644]
cps-rest/src/test/groovy/org/onap/cps/rest/controller/AdminRestControllerSpec.groovy
cps-rest/src/test/groovy/org/onap/cps/rest/controller/CpsRestInputMapperSpec.groovy [new file with mode: 0644]
cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy
cps-ri/pom.xml
cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java
cps-ri/src/main/java/org/onap/cps/spi/utils/SessionManager.java [new file with mode: 0644]
cps-ri/src/main/resources/hibernate.cfg.xml [new file with mode: 0644]
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceConcurrencySpec.groovy
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceIntegrationSpec.groovy
cps-ri/src/test/groovy/org/onap/cps/spi/utils/SessionManagerIntegrationSpec.groovy [new file with mode: 0644]
cps-ri/src/test/resources/hibernate.cfg.xml [new file with mode: 0644]
cps-service/pom.xml
cps-service/src/main/java/org/onap/cps/api/CpsDataService.java
cps-service/src/main/java/org/onap/cps/api/CpsModuleService.java
cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java
cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java
cps-service/src/main/java/org/onap/cps/notification/NotificationService.java
cps-service/src/main/java/org/onap/cps/spi/CpsDataPersistenceService.java
cps-service/src/main/java/org/onap/cps/spi/CpsModulePersistenceService.java
cps-service/src/main/java/org/onap/cps/spi/model/DataNode.java
cps-service/src/main/java/org/onap/cps/spi/model/ExtendedModuleReference.java [deleted file]
cps-service/src/main/java/org/onap/cps/spi/model/ModuleReference.java
cps-service/src/main/java/org/onap/cps/spi/model/SchemaSet.java
cps-service/src/main/java/org/onap/cps/yang/YangTextSchemaSourceSet.java
cps-service/src/main/java/org/onap/cps/yang/YangTextSchemaSourceSetBuilder.java
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy
docs/admin-guide.rst
docs/api/swagger/cps/openapi.yaml
docs/api/swagger/ncmp/openapi-inventory.yaml
docs/api/swagger/ncmp/openapi.yaml
docs/cps-path.rst
docs/deployment.rst
docs/index.rst
docs/overview.rst
docs/release-notes.rst
jacoco-report/pom.xml
pom.xml
releases/3.0.0-container.yaml [new file with mode: 0644]
releases/3.0.0.yaml [new file with mode: 0644]
spotbugs/pom.xml
version.properties

index 185d9c3..a1aa4c9 100644 (file)
@@ -26,7 +26,7 @@
     <modelVersion>4.0.0</modelVersion>
     <groupId>org.onap.cps</groupId>
     <artifactId>checkstyle</artifactId>
-    <version>3.0.0-SNAPSHOT</version>
+    <version>3.1.0-SNAPSHOT</version>
 
     <profiles>
         <profile>
         <snapshotNexusPath>/content/repositories/snapshots/</snapshotNexusPath>
     </properties>
 
+    <build>
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-deploy-plugin</artifactId>
+                    <version>2.8.2</version>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+    </build>
+
     <distributionManagement>
         <repository>
             <id>ecomp-releases</id>
index 50b06b2..193599f 100755 (executable)
@@ -27,7 +27,7 @@
     <parent>
         <groupId>org.onap.cps</groupId>
         <artifactId>cps-parent</artifactId>
-        <version>3.0.0-SNAPSHOT</version>
+        <version>3.1.0-SNAPSHOT</version>
         <relativePath>../cps-parent/pom.xml</relativePath>
     </parent>
 
index 723e2ca..4dfeee8 100644 (file)
@@ -135,4 +135,4 @@ dmi:
         username: ${DMI_USERNAME}\r
         password: ${DMI_PASSWORD}\r
     api:\r
-        base-path: /dmi\r
+        base-path: dmi\r
index 3e5f70d..e468926 100644 (file)
@@ -25,7 +25,7 @@
     <modelVersion>4.0.0</modelVersion>
     <groupId>org.onap.cps</groupId>
     <artifactId>cps-bom</artifactId>
-    <version>3.0.0-SNAPSHOT</version>
+    <version>3.1.0-SNAPSHOT</version>
     <packaging>pom</packaging>
 
     <description>This artifact contains dependencyManagement declarations of all published CPS components.</description>
         <snapshotNexusPath>/content/repositories/snapshots/</snapshotNexusPath>
     </properties>
 
+    <build>
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-deploy-plugin</artifactId>
+                    <version>2.8.2</version>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+    </build>
+
     <distributionManagement>
         <repository>
             <id>ecomp-releases</id>
index 80513ba..d3a033f 100755 (executable)
@@ -2,6 +2,7 @@
 <!--
   ============LICENSE_START=======================================================
   Copyright (c) 2021 Linux Foundation.
+  Modifications Copyright (C) 2020-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.
   ============LICENSE_END=========================================================
 -->
 <project xmlns="http://maven.apache.org/POM/4.0.0"
-    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 
     <modelVersion>4.0.0</modelVersion>
     <groupId>org.onap.cps</groupId>
     <artifactId>cps-dependencies</artifactId>
-    <version>3.0.0-SNAPSHOT</version>
+    <version>3.1.0-SNAPSHOT</version>
     <packaging>pom</packaging>
 
     <name>${project.groupId}:${project.artifactId}</name>
         <mapstruct.version>1.4.2.Final</mapstruct.version>
     </properties>
 
+    <build>
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-deploy-plugin</artifactId>
+                    <version>2.8.2</version>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+    </build>
+
     <distributionManagement>
         <repository>
             <id>ecomp-releases</id>
             <dependency>
                 <groupId>org.springframework.boot</groupId>
                 <artifactId>spring-boot-dependencies</artifactId>
-                <version>2.5.5</version>
+                <version>2.6.4</version>
                 <type>pom</type>
                 <scope>import</scope>
             </dependency>
             <dependency>
                 <groupId>org.springframework.cloud</groupId>
                 <artifactId>spring-cloud-dependencies</artifactId>
-                <version>2020.0.2</version>
+                <version>2021.0.1</version>
                 <type>pom</type>
                 <scope>import</scope>
             </dependency>
                 <artifactId>commons-lang3</artifactId>
                 <version>3.11</version>
             </dependency>
-            <dependency>
-                <groupId>org.modelmapper</groupId>
-                <artifactId>modelmapper</artifactId>
-                <version>2.3.8</version>
-            </dependency>
             <dependency>
                 <groupId>org.jetbrains</groupId>
                 <artifactId>annotations</artifactId>
                 <version>0.18.0</version>
                 <scope>test</scope>
             </dependency>
-            <dependency>
-                <groupId>org.apache.logging.log4j</groupId>
-                <artifactId>log4j-api</artifactId>
-                <version>2.17.1</version>
-            </dependency>
-            <dependency>
-                <groupId>org.apache.logging.log4j</groupId>
-                <artifactId>log4j-to-slf4j</artifactId>
-                <version>2.17.1</version>
-            </dependency>
             <dependency>
                 <groupId>org.mapstruct</groupId>
                 <artifactId>mapstruct</artifactId>
index b9b399c..9bd9588 100644 (file)
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.onap.cps</groupId>
         <artifactId>cps-parent</artifactId>
-        <version>3.0.0-SNAPSHOT</version>
+        <version>3.1.0-SNAPSHOT</version>
         <relativePath>../cps-parent/pom.xml</relativePath>
     </parent>
 
index d82813b..69225ae 100644 (file)
@@ -53,23 +53,23 @@ components:
             $ref: '#/components/schemas/RestInputCmHandle'
         updatedCmHandles:
           type: array
-          example:
-            cmHandle: my-cm-handle
-            cmHandleProperties:
-              add-my-property: add-property
-              update-my-property: updated-property
-              delete-my-property: '~'
-            publicCmHandleProperties:
-              add-my-property: add-property
-              update-my-property: updated-property
-              delete-my-property: '~'
           items:
             $ref: '#/components/schemas/RestInputCmHandle'
+            example:
+              cmHandle: my-cm-handle
+              cmHandleProperties:
+                add-my-property: add-property
+                update-my-property: updated-property
+                delete-my-property: '~'
+              publicCmHandleProperties:
+                add-my-property: add-property
+                update-my-property: updated-property
+                delete-my-property: '~'
         removedCmHandles:
           type: array
           items:
             type: string
-            example: [my-cm-handle1, my-cm-handle2, my-cm-handle3]
+          example: [my-cm-handle1, my-cm-handle2, my-cm-handle3]
 
     RestInputCmHandle:
       required:
@@ -135,7 +135,7 @@ components:
           type: string
           example: my-cm-handle-id
 
-    ModuleReference:
+    RestModuleReference:
       type: object
       title: Module reference details
       properties:
@@ -329,6 +329,18 @@ components:
         sample 3:
           value:
             options: (depth=2,fields=book/authors)
+    topicParamInQuery:
+      name: topic
+      in: query
+      description: topic parameter in query.
+      required: false
+      schema:
+        type: string
+      allowReserved: true
+      examples:
+        sample 1:
+          value:
+            topic: my-topic-name
     contentParamInHeader:
       name: Content-Type
       in: header
index a267fb4..a9d08b7 100755 (executable)
@@ -29,6 +29,7 @@ getResourceDataForPassthroughOperational:
       - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery'
       - $ref: 'components.yaml#/components/parameters/acceptParamInHeader'
       - $ref: 'components.yaml#/components/parameters/optionsParamInQuery'
+      - $ref: 'components.yaml#/components/parameters/topicParamInQuery'
     responses:
       200:
         description: OK
@@ -60,6 +61,7 @@ resourceDataForPassthroughRunning:
       - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery'
       - $ref: 'components.yaml#/components/parameters/acceptParamInHeader'
       - $ref: 'components.yaml#/components/parameters/optionsParamInQuery'
+      - $ref: 'components.yaml#/components/parameters/topicParamInQuery'
     responses:
       200:
         description: OK
@@ -224,7 +226,7 @@ fetchModuleReferencesByCmHandle:
             schema:
               type: array
               items:
-                $ref: 'components.yaml#/components/schemas/ModuleReference'
+                $ref: 'components.yaml#/components/schemas/RestModuleReference'
       400:
         $ref: 'components.yaml#/components/responses/BadRequest'
       401:
index 97305cf..6a700c3 100644 (file)
@@ -27,7 +27,7 @@
     <parent>
         <groupId>org.onap.cps</groupId>
         <artifactId>cps-parent</artifactId>
-        <version>3.0.0-SNAPSHOT</version>
+        <version>3.1.0-SNAPSHOT</version>
         <relativePath>../cps-parent/pom.xml</relativePath>
     </parent>
 
@@ -22,17 +22,27 @@ package org.onap.cps.ncmp.rest.controller;
 
 import org.mapstruct.Mapper;
 import org.mapstruct.Mapping;
-import org.mapstruct.NullValueMappingStrategy;
+import org.mapstruct.NullValueCheckStrategy;
 import org.mapstruct.NullValuePropertyMappingStrategy;
 import org.onap.cps.ncmp.api.models.DmiPluginRegistration;
 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
 import org.onap.cps.ncmp.rest.model.RestDmiPluginRegistration;
 import org.onap.cps.ncmp.rest.model.RestInputCmHandle;
+import org.onap.cps.ncmp.rest.model.RestModuleReference;
+import org.onap.cps.spi.model.ModuleReference;
 
-@Mapper(componentModel = "spring", nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT,
-    nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT)
-public interface RestInputMapper {
+@Mapper(componentModel = "spring")
+public interface NcmpRestInputMapper {
 
+    @Mapping(source = "createdCmHandles", target = "createdCmHandles",
+        nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS,
+        nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT)
+    @Mapping(source = "updatedCmHandles", target = "updatedCmHandles",
+        nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS,
+        nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT)
+    @Mapping(source = "removedCmHandles", target = "removedCmHandles",
+        nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS,
+        nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT)
     DmiPluginRegistration toDmiPluginRegistration(final RestDmiPluginRegistration restDmiPluginRegistration);
 
     @Mapping(source = "cmHandle", target = "cmHandleID")
@@ -40,4 +50,6 @@ public interface RestInputMapper {
     @Mapping(source = "publicCmHandleProperties", target = "publicProperties")
     NcmpServiceCmHandle toNcmpServiceCmHandle(final RestInputCmHandle restInputCmHandle);
 
-}
+    RestModuleReference toRestModuleReference(
+        final ModuleReference moduleReference);
+}
\ No newline at end of file
index 86f4460..0201fad 100755 (executable)
@@ -37,7 +37,6 @@ import javax.validation.Valid;
 import javax.validation.constraints.NotNull;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.modelmapper.ModelMapper;
 import org.onap.cps.ncmp.api.NetworkCmProxyDataService;
 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
 import org.onap.cps.ncmp.rest.api.NetworkCmProxyApi;
@@ -49,7 +48,7 @@ import org.onap.cps.ncmp.rest.model.ConditionProperties;
 import org.onap.cps.ncmp.rest.model.Conditions;
 import org.onap.cps.ncmp.rest.model.ModuleNameAsJsonObject;
 import org.onap.cps.ncmp.rest.model.ModuleNamesAsJsonArray;
-import org.onap.cps.ncmp.rest.model.ModuleReference;
+import org.onap.cps.ncmp.rest.model.RestModuleReference;
 import org.onap.cps.ncmp.rest.model.RestOutputCmHandle;
 import org.onap.cps.utils.JsonObjectMapper;
 import org.springframework.http.HttpStatus;
@@ -65,9 +64,9 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
 
     private static final String NO_BODY = null;
 
-    private final ModelMapper modelMapper;
     private final NetworkCmProxyDataService networkCmProxyDataService;
     private final JsonObjectMapper jsonObjectMapper;
+    private final NcmpRestInputMapper ncmpRestInputMapper;
 
     /**
      * Get resource data from operational datastore.
@@ -76,17 +75,20 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
      * @param resourceIdentifier resource identifier
      * @param acceptParamInHeader accept header parameter
      * @param optionsParamInQuery options query parameter
+     * @param topicParamInQuery topic query parameter
      * @return {@code ResponseEntity} response from dmi plugin
      */
     @Override
     public ResponseEntity<Object> getResourceDataOperationalForCmHandle(final String cmHandle,
                                                                         final @NotNull @Valid String resourceIdentifier,
                                                                         final String acceptParamInHeader,
-                                                                        final @Valid String optionsParamInQuery) {
+                                                                        final @Valid String optionsParamInQuery,
+                                                                        final @Valid String topicParamInQuery) {
         final Object responseObject = networkCmProxyDataService.getResourceDataOperationalForCmHandle(cmHandle,
                 resourceIdentifier,
                 acceptParamInHeader,
-                optionsParamInQuery);
+                optionsParamInQuery,
+                topicParamInQuery);
         return ResponseEntity.ok(responseObject);
     }
 
@@ -97,17 +99,20 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
      * @param resourceIdentifier resource identifier
      * @param acceptParamInHeader accept header parameter
      * @param optionsParamInQuery options query parameter
+     * @param topicParamInQuery topic query parameter
      * @return {@code ResponseEntity} response from dmi plugin
      */
     @Override
     public ResponseEntity<Object> getResourceDataRunningForCmHandle(final String cmHandle,
                                                                     final @NotNull @Valid String resourceIdentifier,
                                                                     final String acceptParamInHeader,
-                                                                    final @Valid String optionsParamInQuery) {
+                                                                    final @Valid String optionsParamInQuery,
+                                                                    final @Valid String topicParamInQuery) {
         final Object responseObject = networkCmProxyDataService.getResourceDataPassThroughRunningForCmHandle(cmHandle,
                 resourceIdentifier,
                 acceptParamInHeader,
-                optionsParamInQuery);
+                optionsParamInQuery,
+                topicParamInQuery);
         return ResponseEntity.ok(responseObject);
     }
 
@@ -205,14 +210,14 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
      * Return module references for a cm handle.
      *
      * @param cmHandle the cm handle
-     * @return module references for cm handle
+     * @return module references for cm handle. Namespace will be always blank because restConf does not include this.
      */
-    public ResponseEntity<List<ModuleReference>> getModuleReferencesByCmHandle(final String cmHandle) {
-        final List<ModuleReference> moduleReferences =
+    public ResponseEntity<List<RestModuleReference>> getModuleReferencesByCmHandle(final String cmHandle) {
+        final List<RestModuleReference> restModuleReferences =
             networkCmProxyDataService.getYangResourcesModuleReferences(cmHandle).stream()
-            .map(moduleReference -> modelMapper.map(moduleReference, ModuleReference.class))
+            .map(ncmpRestInputMapper::toRestModuleReference)
                 .collect(Collectors.toList());
-        return new ResponseEntity<>(moduleReferences, HttpStatus.OK);
+        return new ResponseEntity<>(restModuleReferences, HttpStatus.OK);
     }
 
     private Collection<String> processConditions(final List<ConditionProperties> conditionProperties) {
index 3699195..c9d26f2 100755 (executable)
@@ -37,7 +37,7 @@ import org.springframework.web.bind.annotation.RestController;
 public class NetworkCmProxyInventoryController implements NetworkCmProxyInventoryApi {
 
     private final NetworkCmProxyDataService networkCmProxyDataService;
-    private final RestInputMapper restInputMapper;
+    private final NcmpRestInputMapper ncmpRestInputMapper;
 
     /**
      * Update DMI Plugin Registration (used for first registration also).
@@ -47,7 +47,7 @@ public class NetworkCmProxyInventoryController implements NetworkCmProxyInventor
     public ResponseEntity<Void> updateDmiPluginRegistration(
         final @Valid RestDmiPluginRegistration restDmiPluginRegistration) {
         networkCmProxyDataService.updateDmiRegistrationAndSyncModule(
-            restInputMapper.toDmiPluginRegistration(restDmiPluginRegistration));
+            ncmpRestInputMapper.toDmiPluginRegistration(restDmiPluginRegistration));
         return new ResponseEntity<>(HttpStatus.NO_CONTENT);
     }
 
index 5aaf1c3..0843e97 100755 (executable)
@@ -24,12 +24,14 @@ import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.ncmp.api.impl.exception.DmiRequestException;
+import org.onap.cps.ncmp.api.impl.exception.InvalidTopicException;
 import org.onap.cps.ncmp.api.impl.exception.NcmpException;
 import org.onap.cps.ncmp.api.impl.exception.ServerNcmpException;
 import org.onap.cps.ncmp.rest.controller.NetworkCmProxyController;
 import org.onap.cps.ncmp.rest.controller.NetworkCmProxyInventoryController;
 import org.onap.cps.ncmp.rest.model.ErrorMessage;
 import org.onap.cps.spi.exceptions.CpsException;
+import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
 import org.onap.cps.spi.exceptions.DataValidationException;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
@@ -64,11 +66,17 @@ public class NetworkCmProxyRestExceptionHandler {
         return buildErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, exception);
     }
 
-    @ExceptionHandler({DmiRequestException.class, DataValidationException.class, HttpMessageNotReadableException.class})
+    @ExceptionHandler({DmiRequestException.class, DataValidationException.class, HttpMessageNotReadableException.class,
+            InvalidTopicException.class})
     public static ResponseEntity<Object> handleDmiRequestExceptions(final Exception exception) {
         return buildErrorResponse(HttpStatus.BAD_REQUEST, exception);
     }
 
+    @ExceptionHandler({DataNodeNotFoundException.class})
+    public static ResponseEntity<Object> handleNotFoundExceptions(final CpsException exception) {
+        return buildErrorResponse(HttpStatus.NOT_FOUND, exception);
+    }
+
     private static ResponseEntity<Object> buildErrorResponse(final HttpStatus status, final Exception exception) {
         if (exception.getCause() != null || !(exception instanceof CpsException)) {
             log.error("Exception occurred", exception);
 package org.onap.cps.ncmp.rest.controller
 
 import org.mapstruct.factory.Mappers
+import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
 import org.onap.cps.ncmp.rest.model.RestDmiPluginRegistration
 import org.onap.cps.ncmp.rest.model.RestInputCmHandle
+import org.onap.cps.ncmp.rest.model.RestModuleReference
+import org.onap.cps.spi.model.ModuleReference
 import spock.lang.Specification
 
-class RestInputMapperSpec extends Specification {
+class NcmpRestInputMapperSpec extends Specification {
 
-    def objectUnderTest = Mappers.getMapper(RestInputMapper.class)
+    def objectUnderTest = Mappers.getMapper(NcmpRestInputMapper.class)
 
     def 'Convert a created REST CM Handle Input to an NCMP Service CM Handle with #scenario'() {
         given: 'a rest cm handle input'
@@ -61,4 +64,27 @@ class RestInputMapperSpec extends Specification {
             assert result.removedCmHandles == []
     }
 
+    def 'Handling non-empty dmi registration'() {
+        given: 'a rest cm handle input with cm handles'
+            def restDmiPluginRegistration = new RestDmiPluginRegistration(
+                createdCmHandles: [new RestInputCmHandle()],
+                updatedCmHandles: [new RestInputCmHandle()],
+                removedCmHandles: ["some-cmHandle"]
+            )
+        when: 'to dmi plugin registration is called'
+            def result  = objectUnderTest.toDmiPluginRegistration(restDmiPluginRegistration)
+        then: 'Lists contain values'
+            assert result.createdCmHandles[0].class == NcmpServiceCmHandle.class
+            assert result.updatedCmHandles[0].class == NcmpServiceCmHandle.class
+            assert result.removedCmHandles == ["some-cmHandle"]
+    }
+
+    def 'Convert a ModuleReference to a RestModuleReference'() {
+        given: 'a ModuleReference'
+            def moduleReference = new ModuleReference()
+        when: 'toRestModuleReference is called'
+            def result = objectUnderTest.toRestModuleReference(moduleReference)
+        then: 'the result is of the correct class RestModuleReference'
+            result.class == RestModuleReference.class
+    }
 }
index c997714..d5c3cd9 100644 (file)
@@ -22,8 +22,9 @@
 
 package org.onap.cps.ncmp.rest.controller
 
-
+import org.mapstruct.factory.Mappers
 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
+import spock.lang.Shared
 
 import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.PATCH
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
@@ -36,7 +37,6 @@ import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum
 import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.DELETE
 
 import com.fasterxml.jackson.databind.ObjectMapper
-import org.modelmapper.ModelMapper
 import org.onap.cps.TestUtils
 import org.onap.cps.spi.model.ModuleReference
 import org.onap.cps.utils.JsonObjectMapper
@@ -60,17 +60,20 @@ class NetworkCmProxyControllerSpec extends Specification {
     NetworkCmProxyDataService mockNetworkCmProxyDataService = Mock()
 
     @SpringBean
-    ModelMapper modelMapper = new ModelMapper()
+    JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
 
     @SpringBean
-    JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
+    NcmpRestInputMapper ncmpRestInputMapper = Mappers.getMapper(NcmpRestInputMapper)
 
     @Value('${rest.api.ncmp-base-path}/v1')
     def ncmpBasePathV1
 
     def requestBody = '{"some-key":"some-value"}'
 
-    def 'Get Resource Data from pass-through operational.' () {
+    @Shared
+    def NO_TOPIC = null
+
+    def 'Get Resource Data from pass-through operational.'() {
         given: 'resource data url'
             def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-operational" +
                     "?resourceIdentifier=parent/child&options=(a=1,b=2)"
@@ -84,12 +87,40 @@ class NetworkCmProxyControllerSpec extends Specification {
             1 * mockNetworkCmProxyDataService.getResourceDataOperationalForCmHandle('testCmHandle',
                     'parent/child',
                     'application/json',
-                    '(a=1,b=2)')
+                    '(a=1,b=2)',
+                    NO_TOPIC)
+        and: 'response status is Ok'
+            response.status == HttpStatus.OK.value()
+    }
+
+    def 'Get Resource Data from pass-through operational with #scenario.'() {
+        given: 'resource data url'
+            def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-operational" +
+                    "?resourceIdentifier=parent/child&options=(a=1,b=2)${topicQueryParam}"
+        when: 'get data resource request is performed'
+            def response = mvc.perform(
+                    get(getUrl)
+                    .contentType(MediaType.APPLICATION_JSON)
+                    .accept(MediaType.APPLICATION_JSON_VALUE)
+            ).andReturn().response
+        then: 'the NCMP data service is called with operational data for cm handle'
+            1 * mockNetworkCmProxyDataService.getResourceDataOperationalForCmHandle('testCmHandle',
+                    'parent/child',
+                    'application/json',
+                    '(a=1,b=2)',
+                    expectedTopicName)
         and: 'response status is Ok'
             response.status == HttpStatus.OK.value()
+        where: 'the following parameters are used'
+            scenario               | topicQueryParam        || expectedTopicName
+            'Url with valid topic' | "&topic=my-topic-name" || "my-topic-name"
+            'No topic in url'      | ''                     || NO_TOPIC
+            'Null topic in url'    | "&topic=null"          || "null"
+            'Empty topic in url'   | "&topic=\"\""          || "\"\""
+            'Missing topic in url' | "&topic="              || ""
     }
 
-    def 'Get Resource Data from pass-through running with #scenario value in resource identifier param.' () {
+    def 'Get Resource Data from pass-through running with #scenario value in resource identifier param.'() {
         given: 'resource data url'
             def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
                     "?resourceIdentifier=" + resourceIdentifier + "&options=(a=1,b=2)"
@@ -97,7 +128,8 @@ class NetworkCmProxyControllerSpec extends Specification {
             mockNetworkCmProxyDataService.getResourceDataPassThroughRunningForCmHandle('testCmHandle',
                     resourceIdentifier,
                     'application/json',
-                    '(a=1,b=2)') >> '{valid-json}'
+                    '(a=1,b=2)',
+                    NO_TOPIC) >> '{valid-json}'
         when: 'get data resource request is performed'
             def response = mvc.perform(
                     get(getUrl)
index 079554a..9b1c2e8 100644 (file)
@@ -49,7 +49,7 @@ class NetworkCmProxyInventoryControllerSpec extends Specification {
     NetworkCmProxyDataService mockNetworkCmProxyDataService = Mock()
 
     @SpringBean
-    RestInputMapper restInputMapper = Mock()
+    NcmpRestInputMapper ncmpRestInputMapper = Mock()
 
     DmiPluginRegistration mockDmiPluginRegistration = Mock()
 
@@ -64,7 +64,7 @@ class NetworkCmProxyInventoryControllerSpec extends Specification {
         and: 'the expected rest input as an object'
             def expectedRestDmiPluginRegistration = jsonObjectMapper.convertJsonString(jsonData, RestDmiPluginRegistration)
         and: 'the converter returns a dmi registration (only for the expected input object)'
-            restInputMapper.toDmiPluginRegistration(expectedRestDmiPluginRegistration) >> mockDmiPluginRegistration
+            ncmpRestInputMapper.toDmiPluginRegistration(expectedRestDmiPluginRegistration) >> mockDmiPluginRegistration
         when: 'post request is performed & registration is called with correct DMI plugin information'
             def response = mvc.perform(
                 post("$ncmpBasePathV1/ch")
index 8004328..b642370 100644 (file)
 
 package org.onap.cps.ncmp.rest.exceptions
 
+import com.fasterxml.jackson.databind.ObjectMapper
 import groovy.json.JsonSlurper
-import org.modelmapper.ModelMapper
+import org.mapstruct.factory.Mappers
 import org.onap.cps.TestUtils
 import org.onap.cps.ncmp.api.NetworkCmProxyDataService
 import org.onap.cps.ncmp.api.impl.exception.DmiRequestException
 import org.onap.cps.ncmp.api.impl.exception.ServerNcmpException
-import org.onap.cps.ncmp.rest.controller.RestInputMapper
+import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
+import org.onap.cps.ncmp.rest.controller.NcmpRestInputMapper
 import org.onap.cps.spi.exceptions.CpsException
 import org.onap.cps.spi.exceptions.DataNodeNotFoundException
 import org.onap.cps.spi.exceptions.DataValidationException
@@ -45,6 +47,7 @@ import static org.onap.cps.ncmp.rest.exceptions.NetworkCmProxyRestExceptionHandl
 import static org.onap.cps.ncmp.rest.exceptions.NetworkCmProxyRestExceptionHandlerSpec.ApiType.NCMPINVENTORY
 import static org.springframework.http.HttpStatus.BAD_REQUEST
 import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR
+import static org.springframework.http.HttpStatus.NOT_FOUND
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
 
@@ -57,14 +60,11 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification {
     @SpringBean
     NetworkCmProxyDataService mockNetworkCmProxyDataService = Mock()
 
-    @SpringBean
-    ModelMapper modelMapper = Stub()
-
     @SpringBean
     JsonObjectMapper jsonObjectMapper = Stub()
 
     @SpringBean
-    RestInputMapper restInputMapper = Mock()
+    NcmpRestInputMapper ncmpRestInputMapper = Mappers.getMapper(NcmpRestInputMapper)
 
     @Value('${rest.api.ncmp-base-path}')
     def basePathNcmp
@@ -76,40 +76,39 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification {
     def dataNodeBaseEndpointNcmpInventory
 
     @Shared
-    def errorMessage = 'some error message'
-    @Shared
-    def errorDetails = 'some error details'
+    def sampleErrorMessage = 'some error message'
     @Shared
-    def dataNodeNotFoundErrorMessage = 'DataNode not found'
+    def sampleErrorDetails = 'some error details'
 
     def setup() {
         dataNodeBaseEndpointNcmp = "$basePathNcmp/v1"
         dataNodeBaseEndpointNcmpInventory = "$basePathNcmpInventory/v1"
     }
 
-    def 'Get request with generic #scenario exception returns correct HTTP Status.'() {
+    def 'Get request with generic #scenario exception returns correct HTTP Status with #scenario'() {
         when: 'an exception is thrown by the service'
             setupTestException(exception, NCMP)
             def response = performTestRequest(NCMP)
         then: 'an HTTP response is returned with correct message and details'
             assertTestResponse(response, expectedErrorCode, expectedErrorMessage, expectedErrorDetails)
         where:
-            scenario              | exception                                                                 || expectedErrorDetails | expectedErrorMessage          | expectedErrorCode
-            'CPS'                 | new CpsException(errorMessage, errorDetails)                              || errorDetails         |  errorMessage                 | INTERNAL_SERVER_ERROR
-            'NCMP-server'         | new ServerNcmpException(errorMessage, errorDetails)                       || null                 |  errorMessage                 | INTERNAL_SERVER_ERROR
-            'NCMP-client'         | new DmiRequestException(errorMessage, errorDetails)                       || null                 |  errorMessage                 | BAD_REQUEST
-            'DataNode Validation' | new DataNodeNotFoundException(dataNodeNotFoundErrorMessage, errorDetails) || null                 |  dataNodeNotFoundErrorMessage | BAD_REQUEST
-            'other'               | new IllegalStateException(errorMessage)                                   || null                 |  errorMessage                 | INTERNAL_SERVER_ERROR
+            scenario              | exception                                                        || expectedErrorDetails | expectedErrorMessage | expectedErrorCode
+            'CPS'                 | new CpsException(sampleErrorMessage, sampleErrorDetails)         || sampleErrorDetails   | sampleErrorMessage   | INTERNAL_SERVER_ERROR
+            'NCMP-server'         | new ServerNcmpException(sampleErrorMessage, sampleErrorDetails)  || null                 | sampleErrorMessage   | INTERNAL_SERVER_ERROR
+            'NCMP-client'         | new DmiRequestException(sampleErrorMessage, sampleErrorDetails)  || null                 | sampleErrorMessage   | BAD_REQUEST
+            'DataNode Validation' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || null                 | 'DataNode not found' | NOT_FOUND
+            'other'               | new IllegalStateException(sampleErrorMessage)                    || null                 | sampleErrorMessage   | INTERNAL_SERVER_ERROR
+            'Data Node Not Found' | new DataNodeNotFoundException('myDataspaceName', 'myAnchorName') || 'DataNode not found' | 'DataNode not found' | NOT_FOUND
     }
 
     def 'Post request with exception returns correct HTTP Status.'() {
         given: 'the service throws data validation exception'
-            def exception = new DataValidationException(errorMessage, errorDetails)
+            def exception = new DataValidationException(sampleErrorMessage, sampleErrorDetails)
             setupTestException(exception, NCMPINVENTORY)
         when: 'the HTTP request is made'
             def response = performTestRequest(NCMPINVENTORY)
         then: 'an HTTP response is returned with correct message and details'
-            assertTestResponse(response, BAD_REQUEST, errorMessage, errorDetails)
+            assertTestResponse(response, BAD_REQUEST, sampleErrorMessage, sampleErrorDetails)
     }
 
     def setupTestException(exception, apiType) {
@@ -130,9 +129,9 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification {
     static void assertTestResponse(response, expectedStatus , expectedErrorMessage , expectedErrorDetails) {
         assert response.status == expectedStatus.value()
         def content = new JsonSlurper().parseText(response.contentAsString)
-        assert content['status'] == expectedStatus.toString()
-        assert content['message'] == expectedErrorMessage
-        assert expectedErrorDetails == null || content['details'] == expectedErrorDetails
+        assert content['status'].toString().contains(expectedStatus.toString())
+        assert content['message'].toString().contains(expectedErrorMessage)
+        assert expectedErrorDetails == null || content['details'].toString().contains(expectedErrorDetails)
     }
 
     enum ApiType {
index 5145a12..573c76e 100644 (file)
@@ -26,7 +26,7 @@
     <parent>
         <groupId>org.onap.cps</groupId>
         <artifactId>cps-parent</artifactId>
-        <version>3.0.0-SNAPSHOT</version>
+        <version>3.1.0-SNAPSHOT</version>
         <relativePath>../cps-parent/pom.xml</relativePath>
     </parent>
 
@@ -66,9 +66,5 @@
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-validation</artifactId>
         </dependency>
-        <dependency>
-            <groupId>org.modelmapper</groupId>
-            <artifactId>modelmapper</artifactId>
-        </dependency>
     </dependencies>
 </project>
index 471e97e..7f4c18f 100644 (file)
@@ -3,6 +3,7 @@
  *  Copyright (C) 2021 highstreet technologies GmbH
  *  Modifications Copyright (C) 2021-2022 Nordix Foundation
  *  Modifications Copyright (C) 2021 Pantheon.tech
+ *  Modifications Copyright (C) 2022 Bell Canada
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -26,6 +27,7 @@ import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum
 
 import java.util.Collection;
 import org.onap.cps.ncmp.api.models.DmiPluginRegistration;
+import org.onap.cps.ncmp.api.models.DmiPluginRegistrationResponse;
 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
 import org.onap.cps.spi.model.ModuleReference;
 
@@ -38,8 +40,9 @@ public interface NetworkCmProxyDataService {
      * Registration of New CM Handles.
      *
      * @param dmiPluginRegistration Dmi Plugin Registration
+     * @return dmiPluginRegistrationResponse
      */
-    void updateDmiRegistrationAndSyncModule(DmiPluginRegistration dmiPluginRegistration);
+    DmiPluginRegistrationResponse updateDmiRegistrationAndSyncModule(DmiPluginRegistration dmiPluginRegistration);
 
     /**
      * Get resource data for data store pass-through operational
@@ -49,12 +52,14 @@ public interface NetworkCmProxyDataService {
      * @param resourceIdentifier resource identifier
      * @param acceptParamInHeader accept param
      * @param optionsParamInQuery options query
+     * @param topicParamInQuery topic name for (triggering) async responses
      * @return {@code Object} resource data
      */
     Object getResourceDataOperationalForCmHandle(String cmHandleId,
                                                  String resourceIdentifier,
                                                  String acceptParamInHeader,
-                                                 String optionsParamInQuery);
+                                                 String optionsParamInQuery,
+                                                 String topicParamInQuery);
 
     /**
      * Get resource data for data store pass-through running
@@ -64,12 +69,14 @@ public interface NetworkCmProxyDataService {
      * @param resourceIdentifier resource identifier
      * @param acceptParamInHeader accept param
      * @param optionsParamInQuery options query
+     * @param topicParamInQuery topic query
      * @return {@code Object} resource data
      */
     Object getResourceDataPassThroughRunningForCmHandle(String cmHandleId,
                                                         String resourceIdentifier,
                                                         String acceptParamInHeader,
-                                                        String optionsParamInQuery);
+                                                        String optionsParamInQuery,
+                                                        String topicParamInQuery);
 
     /**
      * Write resource data for data store pass-through running
index 1762e46..c3369d8 100755 (executable)
@@ -3,7 +3,7 @@
  *  Copyright (C) 2021 highstreet technologies GmbH
  *  Modifications Copyright (C) 2021-2022 Nordix Foundation
  *  Modifications Copyright (C) 2021 Pantheon.tech
- *  Modifications Copyright (C) 2021 Bell Canada
+ *  Modifications Copyright (C) 2021-2022 Bell Canada
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -31,12 +31,14 @@ import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NO_TIMES
 import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum;
 import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED;
 
-import com.fasterxml.jackson.core.JsonProcessingException;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.UUID;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -44,19 +46,24 @@ import org.onap.cps.api.CpsAdminService;
 import org.onap.cps.api.CpsDataService;
 import org.onap.cps.api.CpsModuleService;
 import org.onap.cps.ncmp.api.NetworkCmProxyDataService;
+import org.onap.cps.ncmp.api.impl.exception.InvalidTopicException;
 import org.onap.cps.ncmp.api.impl.exception.ServerNcmpException;
 import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations;
 import org.onap.cps.ncmp.api.impl.operations.DmiModelOperations;
 import org.onap.cps.ncmp.api.impl.operations.DmiOperations;
 import org.onap.cps.ncmp.api.impl.operations.YangModelCmHandleRetriever;
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandlesList;
+import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse;
+import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError;
 import org.onap.cps.ncmp.api.models.DmiPluginRegistration;
+import org.onap.cps.ncmp.api.models.DmiPluginRegistrationResponse;
 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
+import org.onap.cps.spi.exceptions.AlreadyDefinedException;
 import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
-import org.onap.cps.spi.exceptions.DataValidationException;
+import org.onap.cps.spi.exceptions.SchemaSetNotFoundException;
 import org.onap.cps.spi.model.ModuleReference;
 import org.onap.cps.utils.JsonObjectMapper;
+import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Service;
 
@@ -81,49 +88,50 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
 
     private final YangModelCmHandleRetriever yangModelCmHandleRetriever;
 
+    // valid kafka topic name regex
+    private static final Pattern TOPIC_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9]([._-](?![._-])|"
+            + "[a-zA-Z0-9]){0,120}[a-zA-Z0-9]$");
+    private static final String NO_REQUEST_ID = null;
+    private static final String NO_TOPIC = null;
+
     @Override
-    public void updateDmiRegistrationAndSyncModule(final DmiPluginRegistration dmiPluginRegistration) {
+    public DmiPluginRegistrationResponse updateDmiRegistrationAndSyncModule(
+        final DmiPluginRegistration dmiPluginRegistration) {
         dmiPluginRegistration.validateDmiPluginRegistration();
-        try {
-            if (!dmiPluginRegistration.getCreatedCmHandles().isEmpty()) {
-                parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(dmiPluginRegistration);
-            }
-            if (!dmiPluginRegistration.getUpdatedCmHandles().isEmpty()) {
-                parseAndUpdateCmHandlesInDmiRegistration(dmiPluginRegistration);
-            }
-            parseAndRemoveCmHandlesInDmiRegistration(dmiPluginRegistration);
-        } catch (final JsonProcessingException | DataNodeNotFoundException e) {
-            final String errorMessage = String.format(
-                    "Error occurred while processing the CM-handle registration request, caused by : [%s]",
-                    e.getMessage());
-            throw new DataValidationException(errorMessage, e.getMessage(), e);
+        final var dmiPluginRegistrationResponse = new DmiPluginRegistrationResponse();
+        dmiPluginRegistrationResponse.setRemovedCmHandles(
+            parseAndRemoveCmHandlesInDmiRegistration(dmiPluginRegistration.getRemovedCmHandles()));
+        if (!dmiPluginRegistration.getCreatedCmHandles().isEmpty()) {
+            dmiPluginRegistrationResponse.setCreatedCmHandles(
+                parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(dmiPluginRegistration));
+        }
+        if (!dmiPluginRegistration.getUpdatedCmHandles().isEmpty()) {
+            dmiPluginRegistrationResponse.setUpdatedCmHandles(
+                networkCmProxyDataServicePropertyHandler
+                    .updateCmHandleProperties(dmiPluginRegistration.getUpdatedCmHandles()));
         }
+        return dmiPluginRegistrationResponse;
     }
 
     @Override
     public Object getResourceDataOperationalForCmHandle(final String cmHandleId,
                                                         final String resourceIdentifier,
                                                         final String acceptParamInHeader,
-                                                        final String optionsParamInQuery) {
-        return handleResponse(dmiDataOperations.getResourceDataFromDmi(
-            cmHandleId,
-            resourceIdentifier,
-            optionsParamInQuery,
-            acceptParamInHeader,
-            DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL), "Not able to get resource data.");
+                                                        final String optionsParamInQuery,
+                                                        final String topicParamInQuery) {
+
+        return validateTopicNameAndGetResourceData(cmHandleId, resourceIdentifier, acceptParamInHeader,
+                DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL, optionsParamInQuery, topicParamInQuery);
     }
 
     @Override
     public Object getResourceDataPassThroughRunningForCmHandle(final String cmHandleId,
                                                                final String resourceIdentifier,
                                                                final String acceptParamInHeader,
-                                                               final String optionsParamInQuery) {
-        return handleResponse(dmiDataOperations.getResourceDataFromDmi(
-            cmHandleId,
-            resourceIdentifier,
-            optionsParamInQuery,
-            acceptParamInHeader,
-            DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING), "Not able to get resource data.");
+                                                               final String optionsParamInQuery,
+                                                               final String topicParamInQuery) {
+        return validateTopicNameAndGetResourceData(cmHandleId, resourceIdentifier, acceptParamInHeader,
+                DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING, optionsParamInQuery, topicParamInQuery);
     }
 
     @Override
@@ -157,6 +165,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
 
     /**
      * Retrieve cm handle details for a given cm handle.
+     *
      * @param cmHandleId cm handle identifier
      * @return cm handle details
      */
@@ -198,14 +207,19 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
      * THis method registers a cm handle and initiates modules sync.
      *
      * @param dmiPluginRegistration dmi plugin registration information.
-     * @throws JsonProcessingException thrown if json is malformed or missing.
+     * @return cm-handle registration response for create cm-handle requests.
      */
-    public void parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(
-        final DmiPluginRegistration dmiPluginRegistration) throws JsonProcessingException {
-        final YangModelCmHandlesList createdYangModelCmHandlesList =
-            getUpdatedYangModelCmHandlesList(dmiPluginRegistration,
-                dmiPluginRegistration.getCreatedCmHandles());
-        registerAndSyncNewCmHandles(createdYangModelCmHandlesList);
+    public List<CmHandleRegistrationResponse> parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(
+        final DmiPluginRegistration dmiPluginRegistration) {
+        return dmiPluginRegistration.getCreatedCmHandles().stream()
+            .map(cmHandle ->
+                YangModelCmHandle.toYangModelCmHandle(
+                    dmiPluginRegistration.getDmiPlugin(),
+                    dmiPluginRegistration.getDmiDataPlugin(),
+                    dmiPluginRegistration.getDmiModelPlugin(), cmHandle)
+            )
+            .map(this::registerAndSyncNewCmHandle)
+            .collect(Collectors.toList());
     }
 
     private static Object handleResponse(final ResponseEntity<?> responseEntity,
@@ -219,27 +233,19 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
         }
     }
 
-    private void parseAndUpdateCmHandlesInDmiRegistration(final DmiPluginRegistration dmiPluginRegistration) {
-        networkCmProxyDataServicePropertyHandler.updateCmHandleProperties(dmiPluginRegistration.getUpdatedCmHandles());
-    }
-
-    private YangModelCmHandlesList getUpdatedYangModelCmHandlesList(
-        final DmiPluginRegistration dmiPluginRegistration,
-        final List<NcmpServiceCmHandle> updatedCmHandles) {
-        return YangModelCmHandlesList.toYangModelCmHandlesList(
-            dmiPluginRegistration.getDmiPlugin(),
-            dmiPluginRegistration.getDmiDataPlugin(),
-            dmiPluginRegistration.getDmiModelPlugin(),
-            updatedCmHandles);
-    }
-
-    private void registerAndSyncNewCmHandles(final YangModelCmHandlesList yangModelCmHandlesList) {
-        final String cmHandleJsonData = jsonObjectMapper.asJsonString(yangModelCmHandlesList);
-        cpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT,
+    private CmHandleRegistrationResponse registerAndSyncNewCmHandle(final YangModelCmHandle yangModelCmHandle) {
+        try {
+            final String cmHandleJsonData = String.format("{\"cm-handles\":[%s]}",
+                jsonObjectMapper.asJsonString(yangModelCmHandle));
+            cpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, NCMP_DMI_REGISTRY_PARENT,
                 cmHandleJsonData, NO_TIMESTAMP);
-
-        for (final YangModelCmHandle yangModelCmHandle : yangModelCmHandlesList.getYangModelCmHandles()) {
             syncModulesAndCreateAnchor(yangModelCmHandle);
+            return CmHandleRegistrationResponse.createSuccessResponse(yangModelCmHandle.getId());
+        } catch (final AlreadyDefinedException alreadyDefinedException) {
+            return CmHandleRegistrationResponse.createFailureResponse(
+                yangModelCmHandle.getId(), RegistrationError.CM_HANDLE_ALREADY_EXIST);
+        } catch (final Exception exception) {
+            return CmHandleRegistrationResponse.createFailureResponse(yangModelCmHandle.getId(), exception);
         }
     }
 
@@ -248,24 +254,37 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
         createAnchor(yangModelCmHandle);
     }
 
-    private void parseAndRemoveCmHandlesInDmiRegistration(final DmiPluginRegistration dmiPluginRegistration) {
-        for (final String cmHandle : dmiPluginRegistration.getRemovedCmHandles()) {
+    protected List<CmHandleRegistrationResponse> parseAndRemoveCmHandlesInDmiRegistration(
+        final List<String> tobeRemovedCmHandles) {
+        final List<CmHandleRegistrationResponse> cmHandleRegistrationResponses =
+            new ArrayList<>(tobeRemovedCmHandles.size());
+        for (final String cmHandle : tobeRemovedCmHandles) {
             try {
-                attemptToDeleteSchemaSetWithCascade(cmHandle);
+                deleteSchemaSetWithCascade(cmHandle);
                 cpsDataService.deleteListOrListElement(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
                     "/dmi-registry/cm-handles[@id='" + cmHandle + "']", NO_TIMESTAMP);
-            } catch (final DataNodeNotFoundException e) {
-                log.warn("Datanode {} not deleted message {}", cmHandle, e.getMessage());
+                cmHandleRegistrationResponses.add(CmHandleRegistrationResponse.createSuccessResponse(cmHandle));
+            } catch (final DataNodeNotFoundException dataNodeNotFoundException) {
+                log.error("Unable to find dataNode for cmHandleId : {} , caused by : {}",
+                    cmHandle, dataNodeNotFoundException.getMessage());
+                cmHandleRegistrationResponses.add(CmHandleRegistrationResponse
+                    .createFailureResponse(cmHandle, RegistrationError.CM_HANDLE_DOES_NOT_EXIST));
+            } catch (final Exception exception) {
+                log.error("Unable to de-register cm-handleIdd : {} , caused by : {}",
+                    cmHandle, exception.getMessage());
+                cmHandleRegistrationResponses.add(
+                    CmHandleRegistrationResponse.createFailureResponse(cmHandle, exception));
             }
         }
+        return cmHandleRegistrationResponses;
     }
 
-    private void attemptToDeleteSchemaSetWithCascade(final String schemaSetName) {
+    private void deleteSchemaSetWithCascade(final String schemaSetName) {
         try {
             cpsModuleService.deleteSchemaSet(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, schemaSetName,
                 CASCADE_DELETE_ALLOWED);
-        } catch (final Exception e) {
-            log.warn("Schema set {} delete failed, reason {}", schemaSetName, e.getMessage());
+        } catch (final SchemaSetNotFoundException schemaSetNotFoundException) {
+            log.warn("Schema set {} does not exist or already deleted", schemaSetName);
         }
     }
 
@@ -297,4 +316,38 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
         cpsAdminService.createAnchor(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, yangModelCmHandle.getId(),
             yangModelCmHandle.getId());
     }
-}
+
+    private static boolean hasTopicParameter(final String topicName) {
+        if (topicName == null) {
+            return false;
+        }
+        if (TOPIC_NAME_PATTERN.matcher(topicName).matches()) {
+            return true;
+        }
+        throw new InvalidTopicException("Topic name " + topicName + " is invalid", "invalid topic");
+    }
+
+    private Map<String, Object> buildDmiResponse(final String requestId) {
+        final Map<String, Object> dmiResponseMap = new HashMap<>();
+        dmiResponseMap.put("requestId", requestId);
+        return dmiResponseMap;
+    }
+
+    private Object validateTopicNameAndGetResourceData(final String cmHandleId,
+                                                       final String resourceIdentifier,
+                                                       final String acceptParamInHeader,
+                                                       final DmiOperations.DataStoreEnum dataStore,
+                                                       final String optionsParamInQuery,
+                                                       final String topicParamInQuery) {
+        final boolean processAsynchronously = hasTopicParameter(topicParamInQuery);
+        if (processAsynchronously) {
+            final String resourceDataRequestId = UUID.randomUUID().toString();
+            return ResponseEntity.status(HttpStatus.OK)
+                    .body(buildDmiResponse(resourceDataRequestId));
+        }
+        final ResponseEntity<?> responseEntity = dmiDataOperations.getResourceDataFromDmi(
+                cmHandleId, resourceIdentifier, optionsParamInQuery, acceptParamInHeader,
+                dataStore, NO_REQUEST_ID, NO_TOPIC);
+        return handleResponse(responseEntity, "Not able to get resource data.");
+    }
+}
\ No newline at end of file
index ca2f578..c838a75 100644 (file)
@@ -1,6 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2022 Nordix Foundation
+ *  Modifications Copyright (C) 2022 Bell Canada
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -28,15 +29,19 @@ import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NCMP_DMI
 import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NO_TIMESTAMP;
 
 import com.google.common.collect.ImmutableMap;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.api.CpsDataService;
+import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse;
+import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError;
 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
 import org.onap.cps.spi.FetchDescendantsOption;
 import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
@@ -61,23 +66,31 @@ public class NetworkCmProxyDataServicePropertyHandler {
      *
      * @param ncmpServiceCmHandles collection of ncmpServiceCmHandles
      */
-    public void updateCmHandleProperties(final Collection<NcmpServiceCmHandle> ncmpServiceCmHandles)
-        throws DataNodeNotFoundException {
+    public List<CmHandleRegistrationResponse> updateCmHandleProperties(
+        final Collection<NcmpServiceCmHandle> ncmpServiceCmHandles) {
+        final List<CmHandleRegistrationResponse> cmHandleRegistrationResponses = new ArrayList<>();
         for (final NcmpServiceCmHandle ncmpServiceCmHandle : ncmpServiceCmHandles) {
+            final String cmHandle = ncmpServiceCmHandle.getCmHandleID();
             try {
-                final String cmHandleXpath = String.format(CM_HANDLE_XPATH_TEMPLATE,
-                    ncmpServiceCmHandle.getCmHandleID());
+                final String cmHandleXpath = String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandle);
                 final DataNode existingCmHandleDataNode =
                         cpsDataService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, cmHandleXpath,
                                 FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS);
                 processUpdates(existingCmHandleDataNode, ncmpServiceCmHandle);
+                cmHandleRegistrationResponses.add(CmHandleRegistrationResponse.createSuccessResponse(cmHandle));
             } catch (final DataNodeNotFoundException e) {
                 log.error("Unable to find dataNode for cmHandleId : {} , caused by : {}",
-                    ncmpServiceCmHandle.getCmHandleID(),
-                        e.getMessage());
-                throw e;
+                    cmHandle, e.getMessage());
+                cmHandleRegistrationResponses.add(CmHandleRegistrationResponse
+                    .createFailureResponse(cmHandle, RegistrationError.CM_HANDLE_DOES_NOT_EXIST));
+            } catch (final Exception exception) {
+                log.error("Unable to update dataNode for cmHandleId : {} , caused by : {}",
+                    cmHandle, exception.getMessage());
+                cmHandleRegistrationResponses.add(
+                    CmHandleRegistrationResponse.createFailureResponse(cmHandle, exception));
             }
         }
+        return cmHandleRegistrationResponses;
     }
 
     private void processUpdates(final DataNode existingCmHandleDataNode, final NcmpServiceCmHandle incomingCmHandle) {
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/InvalidTopicException.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/InvalidTopicException.java
new file mode 100644 (file)
index 0000000..b56ca7b
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ *  ============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.exception;
+
+import lombok.Getter;
+
+public class InvalidTopicException extends RuntimeException {
+
+    @Getter
+    final String details;
+
+    /**
+     * Constructor.
+     *
+     * @param message the error message
+     * @param details the error details
+     */
+    public InvalidTopicException(final String message, final String details) {
+        super(message);
+        this.details = details;
+    }
+}
index 229d4fc..68de9d5 100644 (file)
@@ -23,10 +23,10 @@ package org.onap.cps.ncmp.api.impl.operations;
 import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING;
 import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum;
 import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.READ;
-import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.DATA;
 
 import org.onap.cps.ncmp.api.impl.client.DmiRestClient;
 import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration;
+import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder;
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
 import org.onap.cps.utils.JsonObjectMapper;
 import org.springframework.http.HttpHeaders;
@@ -47,8 +47,8 @@ public class DmiDataOperations extends DmiOperations {
     public DmiDataOperations(final YangModelCmHandleRetriever cmHandlePropertiesRetriever,
                              final JsonObjectMapper jsonObjectMapper,
                              final NcmpConfiguration.DmiProperties dmiProperties,
-                             final DmiRestClient dmiRestClient) {
-        super(cmHandlePropertiesRetriever, jsonObjectMapper, dmiProperties, dmiRestClient);
+                             final DmiRestClient dmiRestClient, final DmiServiceUrlBuilder dmiServiceUrlBuilder) {
+        super(cmHandlePropertiesRetriever, jsonObjectMapper, dmiProperties, dmiRestClient, dmiServiceUrlBuilder);
     }
 
     /**
@@ -59,25 +59,31 @@ public class DmiDataOperations extends DmiOperations {
      * @param resourceId  resource identifier
      * @param optionsParamInQuery options query
      * @param acceptParamInHeader accept parameter
-     * @param dataStore  data store enum
+     * @param dataStore           data store enum
+     * @param requestId           requestId for async responses
+     * @param topicParamInQuery   topic name for (triggering) async responses
      * @return {@code ResponseEntity} response entity
      */
     public ResponseEntity<Object> getResourceDataFromDmi(final String cmHandleId,
-                                                          final String resourceId,
-                                                          final String optionsParamInQuery,
-                                                          final String acceptParamInHeader,
-                                                          final DataStoreEnum dataStore) {
+                                                         final String resourceId,
+                                                         final String optionsParamInQuery,
+                                                         final String acceptParamInHeader,
+                                                         final DataStoreEnum dataStore,
+                                                         final String requestId,
+                                                         final String topicParamInQuery) {
         final YangModelCmHandle yangModelCmHandle =
-            yangModelCmHandleRetriever.getDmiServiceNamesAndProperties(cmHandleId);
+                yangModelCmHandleRetriever.getDmiServiceNamesAndProperties(cmHandleId);
         final DmiRequestBody dmiRequestBody = DmiRequestBody.builder()
             .operation(READ)
+            .requestId(requestId)
             .build();
         dmiRequestBody.asDmiProperties(yangModelCmHandle.getDmiProperties());
         final String jsonBody = jsonObjectMapper.asJsonString(dmiRequestBody);
 
-        final var dmiResourceDataUrl = getDmiDatastoreUrlWithOptions(
-            yangModelCmHandle.resolveDmiServiceName(DATA), cmHandleId, resourceId,
-            optionsParamInQuery, dataStore);
+        final var dmiResourceDataUrl = dmiServiceUrlBuilder.getDmiDatastoreUrl(
+                dmiServiceUrlBuilder.populateQueryParams(resourceId, optionsParamInQuery,
+                topicParamInQuery), dmiServiceUrlBuilder.populateUriVariables(
+                        yangModelCmHandle, cmHandleId, dataStore));
         final var httpHeaders = prepareHeader(acceptParamInHeader);
         return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonBody, httpHeaders);
     }
@@ -108,33 +114,10 @@ public class DmiDataOperations extends DmiOperations {
         dmiRequestBody.asDmiProperties(yangModelCmHandle.getDmiProperties());
         final String jsonBody = jsonObjectMapper.asJsonString(dmiRequestBody);
         final String dmiUrl =
-            getResourceInDataStoreUrl(yangModelCmHandle.resolveDmiServiceName(DATA),
-                cmHandleId, resourceId, PASSTHROUGH_RUNNING);
+                dmiServiceUrlBuilder.getDmiDatastoreUrl(dmiServiceUrlBuilder.populateQueryParams(resourceId,
+                                null, null),
+                        dmiServiceUrlBuilder.populateUriVariables(yangModelCmHandle, cmHandleId, PASSTHROUGH_RUNNING));
         return dmiRestClient.postOperationWithJsonData(dmiUrl, jsonBody, new HttpHeaders());
     }
 
-    private String getResourceInDataStoreUrl(final String dmiServiceName,
-                                             final String cmHandleId,
-                                             final String resourceId,
-                                             final DataStoreEnum dataStoreEnum) {
-        return getCmHandleUrl(dmiServiceName, cmHandleId)
-            + "data"
-            + URL_SEPARATOR
-            + "ds"
-            + URL_SEPARATOR
-            + dataStoreEnum.getValue()
-            + "?resourceIdentifier="
-            + resourceId;
-    }
-
-    private String getDmiDatastoreUrlWithOptions(final String dmiServiceName,
-                                                 final String cmHandleId,
-                                                 final String resourceId,
-                                                 final String optionsParamInQuery,
-                                                 final DataStoreEnum dataStoreEnum) {
-        final String resourceInDataStoreUrl = getResourceInDataStoreUrl(dmiServiceName,
-            cmHandleId, resourceId, dataStoreEnum);
-        return appendOptionsQuery(resourceInDataStoreUrl, optionsParamInQuery);
-    }
-
 }
index bfe934d..d79988e 100644 (file)
@@ -31,6 +31,7 @@ import java.util.List;
 import java.util.Map;
 import org.onap.cps.ncmp.api.impl.client.DmiRestClient;
 import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration;
+import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder;
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
 import org.onap.cps.ncmp.api.models.YangResource;
 import org.onap.cps.spi.model.ModuleReference;
@@ -53,8 +54,8 @@ public class DmiModelOperations extends DmiOperations {
     public DmiModelOperations(final YangModelCmHandleRetriever dmiPropertiesRetriever,
                               final JsonObjectMapper jsonObjectMapper,
                               final NcmpConfiguration.DmiProperties dmiProperties,
-                              final DmiRestClient dmiRestClient) {
-        super(dmiPropertiesRetriever, jsonObjectMapper, dmiProperties, dmiRestClient);
+                              final DmiRestClient dmiRestClient, final DmiServiceUrlBuilder dmiServiceUrlBuilder) {
+        super(dmiPropertiesRetriever, jsonObjectMapper, dmiProperties, dmiRestClient, dmiServiceUrlBuilder);
     }
 
     /**
index 645d979..75ba91b 100644 (file)
 
 package org.onap.cps.ncmp.api.impl.operations;
 
-import com.google.common.base.Strings;
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.ncmp.api.impl.client.DmiRestClient;
 import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration;
+import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder;
 import org.onap.cps.utils.JsonObjectMapper;
 import org.springframework.http.HttpHeaders;
 import org.springframework.stereotype.Service;
 
-@Slf4j
 @RequiredArgsConstructor
 @Service
 public class DmiOperations {
@@ -39,7 +37,7 @@ public class DmiOperations {
     public enum DataStoreEnum {
         PASSTHROUGH_OPERATIONAL("ncmp-datastore:passthrough-operational"),
         PASSTHROUGH_RUNNING("ncmp-datastore:passthrough-running");
-        private String value;
+        private final String value;
 
         DataStoreEnum(final String value) {
             this.value = value;
@@ -50,30 +48,12 @@ public class DmiOperations {
     protected final JsonObjectMapper jsonObjectMapper;
     protected final NcmpConfiguration.DmiProperties dmiProperties;
     protected final DmiRestClient dmiRestClient;
-
-    static final String URL_SEPARATOR = "/";
-
-    String getCmHandleUrl(final String dmiServiceName, final String cmHandle) {
-        return dmiServiceName
-            + dmiProperties.getDmiBasePath()
-            + URL_SEPARATOR
-            + "v1"
-            + URL_SEPARATOR
-            + "ch"
-            + URL_SEPARATOR
-            + cmHandle
-            + URL_SEPARATOR;
-    }
+    protected final DmiServiceUrlBuilder dmiServiceUrlBuilder;
 
     String getDmiResourceUrl(final String dmiServiceName, final String cmHandle, final String resourceName) {
-        return getCmHandleUrl(dmiServiceName, cmHandle) + resourceName;
-    }
-
-    static String appendOptionsQuery(final String url, final String optionsParamInQuery) {
-        if (Strings.isNullOrEmpty(optionsParamInQuery)) {
-            return url;
-        }
-        return url + "&options=" + optionsParamInQuery;
+        return dmiServiceUrlBuilder.getCmHandleUrl()
+                .pathSegment("{resourceName}")
+                .buildAndExpand(dmiServiceName, dmiProperties.getDmiBasePath(), cmHandle, resourceName).toUriString();
     }
 
     static HttpHeaders prepareHeader(final String acceptParam) {
index d97e90c..c84e4cb 100644 (file)
@@ -58,6 +58,7 @@ public class DmiRequestBody {
     private String data;
     @JsonProperty("cmHandleProperties")
     private Map<String, String> dmiProperties;
+    private String requestId;
 
     /**
      * Set DMI Properties by converting a list of YangModelCmHandle.Property objects.
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java
new file mode 100644 (file)
index 0000000..b60aac9
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ *  ============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.utils;
+
+import static org.onap.cps.ncmp.api.impl.operations.RequiredDmiService.DATA;
+
+import java.util.HashMap;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import org.apache.logging.log4j.util.Strings;
+import org.apache.logging.log4j.util.TriConsumer;
+import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration;
+import org.onap.cps.ncmp.api.impl.operations.DmiOperations;
+import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
+import org.springframework.stereotype.Component;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.util.UriComponentsBuilder;
+
+@Component
+@RequiredArgsConstructor
+public class DmiServiceUrlBuilder {
+
+    private final NcmpConfiguration.DmiProperties dmiProperties;
+
+    /**
+     * This method creates the dmi service url.
+     *
+     * @param queryParams  query param map as key,value pair
+     * @param uriVariables uri param map as key (placeholder),value pair
+     * @return {@code String} dmi service url as string
+     */
+    public String getDmiDatastoreUrl(final MultiValueMap<String, String> queryParams,
+                                     final Map<String, Object> uriVariables) {
+        final UriComponentsBuilder uriComponentsBuilder = getCmHandleUrl()
+                .pathSegment("data")
+                .pathSegment("ds")
+                .pathSegment("{dataStore}")
+                .queryParams(queryParams)
+                .uriVariables(uriVariables);
+        return uriComponentsBuilder.buildAndExpand().toUriString();
+    }
+
+    /**
+     * This method creates the dmi service url builder object with path variables.
+     *
+     * @return {@code UriComponentsBuilder} dmi service url builder object
+     */
+    public UriComponentsBuilder getCmHandleUrl() {
+        return UriComponentsBuilder.newInstance()
+                .path("{dmiServiceName}")
+                .pathSegment("{dmiBasePath}")
+                .pathSegment("v1")
+                .pathSegment("ch")
+                .pathSegment("{cmHandle}");
+    }
+
+    /**
+     * This method populates uri variables.
+     *
+     * @param yangModelCmHandle get dmi service name
+     * @param cmHandle          cm handle name for dmi registration
+     * @return {@code String} dmi service url as string
+     */
+    public Map<String, Object> populateUriVariables(final YangModelCmHandle yangModelCmHandle,
+                                                    final String cmHandle,
+                                                    final DmiOperations.DataStoreEnum dataStore) {
+        final Map<String, Object> uriVariables = new HashMap<>();
+        final String dmiBasePath = dmiProperties.getDmiBasePath();
+        uriVariables.put("dmiServiceName",
+                yangModelCmHandle.resolveDmiServiceName(DATA));
+        uriVariables.put("dmiBasePath", dmiBasePath);
+        uriVariables.put("cmHandle", cmHandle);
+        uriVariables.put("dataStore", dataStore.getValue());
+        return uriVariables;
+    }
+
+    /**
+     * This method is used to populate map from query params.
+     *
+     * @param resourceId          unique id of response for valid topic
+     * @param optionsParamInQuery options into url param
+     * @param topicParamInQuery   topic into url param
+     * @return all valid query params as map
+     */
+    public MultiValueMap<String, String> populateQueryParams(final String resourceId,
+                                                             final String optionsParamInQuery,
+                                                             final String topicParamInQuery) {
+        final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
+        getQueryParamConsumer().accept("resourceIdentifier",
+                resourceId, queryParams);
+        getQueryParamConsumer().accept("options", optionsParamInQuery, queryParams);
+        if (Strings.isNotEmpty(topicParamInQuery)) {
+            getQueryParamConsumer().accept("topic", topicParamInQuery, queryParams);
+        }
+        return queryParams;
+    }
+
+    private TriConsumer<String, String, MultiValueMap<String, String>> getQueryParamConsumer() {
+        return (paramName, paramValue, paramMap) -> {
+            if (Strings.isNotEmpty(paramValue)) {
+                paramMap.add(paramName, paramValue);
+            }
+        };
+    }
+}
index 47062b3..e46b9e3 100644 (file)
@@ -21,6 +21,8 @@
 
 package org.onap.cps.ncmp.api.impl.yangmodels;
 
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.common.base.Strings;
 import java.util.ArrayList;
@@ -41,6 +43,7 @@ import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
 @Getter
 @Setter
 @NoArgsConstructor
+@JsonInclude(Include.NON_NULL)
 public class YangModelCmHandle {
 
     private String id;
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandlesList.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/yangmodels/YangModelCmHandlesList.java
deleted file mode 100644 (file)
index 261a018..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2022 Nordix Foundation
- *  ================================================================================
- *  Licensed under the Apache License, Version 2.0 (the "License");
- *  you may not use this file except in compliance with the License.
- *  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.yangmodels;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import lombok.Getter;
-import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
-
-@Getter
-public class YangModelCmHandlesList {
-
-    @JsonProperty("cm-handles")
-    private final List<YangModelCmHandle> yangModelCmHandles = new ArrayList<>();
-
-    /**
-     * Create a YangModelCmHandleList given all service names and a collection of cmHandles.
-     * @param dmiServiceName the dmi service name
-     * @param dmiDataServiceName the dmi data service name
-     * @param dmiModelServiceName the dmi model service name
-     * @param ncmpServiceCmHandles cm handles rest model
-     * @return instance of YangModelCmHandleList
-     */
-    public static YangModelCmHandlesList toYangModelCmHandlesList(final String dmiServiceName,
-                                                                  final String dmiDataServiceName,
-                                                                  final String dmiModelServiceName,
-                                                                  final Collection<NcmpServiceCmHandle>
-                                                            ncmpServiceCmHandles) {
-        final YangModelCmHandlesList yangModelCmHandlesList = new YangModelCmHandlesList();
-        for (final NcmpServiceCmHandle ncmpServiceCmHandle : ncmpServiceCmHandles) {
-            final YangModelCmHandle yangModelCmHandle =
-                YangModelCmHandle.toYangModelCmHandle(
-                    dmiServiceName,
-                    dmiDataServiceName,
-                    dmiModelServiceName,
-                    ncmpServiceCmHandle);
-            yangModelCmHandlesList.add(yangModelCmHandle);
-        }
-        return yangModelCmHandlesList;
-    }
-
-    /**
-     * Add a yangModelCmHandle.
-     *
-     * @param yangModelCmHandle the yangModelCmHandle to add
-     */
-    public void add(final YangModelCmHandle yangModelCmHandle) {
-        yangModelCmHandles.add(yangModelCmHandle);
-    }
-}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponse.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponse.java
new file mode 100644 (file)
index 0000000..e183ed1
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2022 Bell Canada
+ *  ================================================================================
+ *  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.models;
+
+import lombok.Builder;
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+
+@Data
+@Builder
+public class CmHandleRegistrationResponse {
+
+    private final String cmHandle;
+    private final Status status;
+    private RegistrationError registrationError;
+    private String errorText;
+
+    /**
+     * Creates a failure response based on exception.
+     *
+     * @param cmHandle  cmHandle
+     * @param exception exception
+     * @return CmHandleRegistrationResponse
+     */
+    public static CmHandleRegistrationResponse createFailureResponse(final String cmHandle, final Exception exception) {
+        return CmHandleRegistrationResponse.builder()
+            .cmHandle(cmHandle)
+            .status(Status.FAILURE)
+            .registrationError(RegistrationError.UNKNOWN_ERROR)
+            .errorText(exception.getMessage()).build();
+    }
+
+    /**
+     * Creates a failure response based on registration error.
+     *
+     * @param cmHandle          cmHandle
+     * @param registrationError registrationError
+     * @return CmHandleRegistrationResponse
+     */
+    public static CmHandleRegistrationResponse createFailureResponse(final String cmHandle,
+        final RegistrationError registrationError) {
+        return CmHandleRegistrationResponse.builder().cmHandle(cmHandle)
+            .status(Status.FAILURE)
+            .registrationError(registrationError)
+            .errorText(registrationError.errorText)
+            .build();
+    }
+
+    public static CmHandleRegistrationResponse createSuccessResponse(final String cmHandle) {
+        return CmHandleRegistrationResponse.builder().cmHandle(cmHandle)
+            .status(Status.SUCCESS).build();
+    }
+
+    public enum Status {
+        SUCCESS, FAILURE;
+    }
+
+    @RequiredArgsConstructor
+    public enum RegistrationError {
+        UNKNOWN_ERROR("00", "Unknown error"),
+        CM_HANDLE_ALREADY_EXIST("01", "cm-handle already exists"),
+        CM_HANDLE_DOES_NOT_EXIST("02", "cm-handle does not exist");
+
+        public final String errorCode;
+        public final String errorText;
+
+    }
+}
\ No newline at end of file
@@ -1,7 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021 Nordix Foundation
- *  Modifications Copyright (C) 2021 Bell Canada.
+ *  Copyright (C) 2022 Bell Canada
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
  *  ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.config
+package org.onap.cps.ncmp.api.models;
 
-import org.modelmapper.ModelMapper
-import spock.lang.Specification
+import java.util.List;
+import lombok.Data;
+import lombok.NoArgsConstructor;
 
-class CpsConfigSpec extends Specification {
-    def objectUnderTest = new CpsConfig()
-
-    def 'CPS configuration has a Model Mapper'() {
-        expect: 'the CPS configuration has a Model Mapper'
-            objectUnderTest.modelMapper() instanceof ModelMapper
-    }
-}
+@Data
+@NoArgsConstructor
+public class DmiPluginRegistrationResponse {
+    private List<CmHandleRegistrationResponse> createdCmHandles;
+    private List<CmHandleRegistrationResponse> updatedCmHandles;
+    private List<CmHandleRegistrationResponse> removedCmHandles;
+}
\ No newline at end of file
index e410463..e7c1d05 100644 (file)
@@ -1,6 +1,7 @@
 /*
  * ============LICENSE_START=======================================================
  *  Copyright (C) 2021-2022 Nordix Foundation
+ *  Modifications Copyright (C) 2022 Bell Canada
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -20,8 +21,8 @@
 
 package org.onap.cps.ncmp.api.impl
 
-import com.fasterxml.jackson.core.JsonProcessingException
 import com.fasterxml.jackson.databind.ObjectMapper
+import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
 import org.onap.cps.api.CpsAdminService
 import org.onap.cps.api.CpsDataService
 import org.onap.cps.api.CpsModuleService
@@ -29,14 +30,20 @@ import org.onap.cps.ncmp.api.impl.exception.DmiRequestException
 import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations
 import org.onap.cps.ncmp.api.impl.operations.DmiModelOperations
 import org.onap.cps.ncmp.api.impl.operations.YangModelCmHandleRetriever
+import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse
 import org.onap.cps.ncmp.api.models.DmiPluginRegistration
 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
+import org.onap.cps.spi.exceptions.AlreadyDefinedException
 import org.onap.cps.spi.exceptions.DataNodeNotFoundException
-import org.onap.cps.spi.exceptions.DataValidationException
+import org.onap.cps.spi.exceptions.SchemaSetNotFoundException
 import org.onap.cps.utils.JsonObjectMapper
 import spock.lang.Shared
 import spock.lang.Specification
 
+import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.CM_HANDLE_DOES_NOT_EXIST
+import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.CM_HANDLE_ALREADY_EXIST
+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 {
@@ -57,102 +64,54 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
     def mockYangModelCmHandleRetriever = Mock(YangModelCmHandleRetriever)
 
     def noTimestamp = null
+    def objectUnderTest = getObjectUnderTestWithModelSyncDisabled()
 
-    def 'Register or re-register a DMI Plugin for the given cm-handle(s) with #scenario process.'() {
-        given: 'a registration'
-            def objectUnderTest = getObjectUnderTestWithModelSyncDisabled()
-            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin:'my-server')
-            ncmpServiceCmHandle.cmHandleID = '123'
-            ncmpServiceCmHandle.dmiProperties = [dmiProp1: 'dmiValue1', dmiProp2: 'dmiValue2']
-            ncmpServiceCmHandle.publicProperties = [publicProp1: 'publicValue1', publicProp2: 'publicValue2' ]
-            dmiPluginRegistration.createdCmHandles = createdCmHandles
-            dmiPluginRegistration.updatedCmHandles = updatedCmHandles
-            dmiPluginRegistration.removedCmHandles = removedCmHandles
-            def expectedJsonData = '{"cm-handles":[{"id":"123","dmi-service-name":"my-server","dmi-data-service-name":null,"dmi-model-service-name":null,' +
-                '"additional-properties":[{"name":"dmiProp1","value":"dmiValue1"},{"name":"dmiProp2","value":"dmiValue2"}],' +
-                '"public-properties":[{"name":"publicProp1","value":"publicValue1"},{"name":"publicProp2","value":"publicValue2"}]' +
-                '}]}'
-        when: 'registration is updated and modules are synced'
-            objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
-        then: 'save list elements is invoked with the expected parameters'
-            expectedCallsToSaveNode * mockCpsDataService.saveListElements('NCMP-Admin', 'ncmp-dmi-registry',
-                '/dmi-registry', expectedJsonData, noTimestamp)
-        and: 'update data node leaves is called with correct parameters'
-            expectedCallsToUpdateCmHandleProperty * mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(updatedCmHandles)
-        and: 'delete schema set is invoked with the correct parameters'
-            expectedCallsToDeleteSchemaSetAndListElement * mockCpsModuleService.deleteSchemaSet('NFP-Operational', 'cmHandle001', CASCADE_DELETE_ALLOWED)
-        and: 'delete list or list element is invoked with the correct parameters'
-            expectedCallsToDeleteSchemaSetAndListElement * mockCpsDataService.deleteListOrListElement('NCMP-Admin',
-                    'ncmp-dmi-registry', "/dmi-registry/cm-handles[@id='cmHandle001']", noTimestamp)
-        where:
-            scenario                    | createdCmHandles      | updatedCmHandles      | removedCmHandles || expectedCallsToSaveNode | expectedCallsToDeleteSchemaSetAndListElement | expectedCallsToUpdateCmHandleProperty
-            'create'                    | [ncmpServiceCmHandle] | []                    | []               || 1                       | 0                                            | 0
-            'update'                    | []                    | [ncmpServiceCmHandle] | []               || 0                       | 0                                            | 1
-            'delete'                    | []                    | []                    | cmHandlesArray   || 0                       | 1                                            | 0
-            'create, update and delete' | [ncmpServiceCmHandle] | [ncmpServiceCmHandle] | cmHandlesArray   || 1                       | 1                                            | 1
-            'no valid data'             | []                    | []                    | []               || 0                       | 0                                            | 0
+    def 'DMI Registration: Create, Update & Delete operations are processed in the right order'() {
+        given: 'a registration with operations of all three types'
+            def dmiRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server')
+            dmiRegistration.setCreatedCmHandles([new NcmpServiceCmHandle(cmHandleID: 'cmhandle-1', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])])
+            dmiRegistration.setUpdatedCmHandles([new NcmpServiceCmHandle(cmHandleID: 'cmhandle-2', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])])
+            dmiRegistration.setRemovedCmHandles(['cmhandle-2'])
+        when: 'registration is processed'
+            objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration)
+            // Spock validated invocation order between multiple then blocks
+        then: 'cm-handles are removed first'
+            1 * objectUnderTest.parseAndRemoveCmHandlesInDmiRegistration(*_)
+        then: 'cm-handles are created'
+            1 * objectUnderTest.parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(*_)
+        then: 'cm-handles are updated'
+            1 * mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(*_)
     }
 
-    def 'Register a DMI Plugin for the given cm-handle(s) without DMI properties.'() {
-        given: 'a registration without cm-handle properties'
-            NetworkCmProxyDataServiceImpl objectUnderTest = getObjectUnderTestWithModelSyncDisabled()
-            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin:'my-server')
-            ncmpServiceCmHandle.cmHandleID = '123'
-            ncmpServiceCmHandle.dmiProperties = Collections.emptyMap()
-            ncmpServiceCmHandle.publicProperties = Collections.emptyMap()
-            dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle]
-            def expectedJsonData = '{"cm-handles":[{"id":"123","dmi-service-name":"my-server","dmi-data-service-name":null,"dmi-model-service-name":null,"additional-properties":[],"public-properties":[]}]}'
-        when: 'registration is updated'
-            objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
-        then: 'save list elements is invoked with the expected parameters'
-            1 * mockCpsDataService.saveListElements('NCMP-Admin', 'ncmp-dmi-registry',
-                    '/dmi-registry', expectedJsonData, noTimestamp)
-    }
+    def 'DMI Registration: Response from all operations types are in response'() {
+        given: 'a registration with operations of all three types'
+            def dmiRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server')
+            dmiRegistration.setCreatedCmHandles([new NcmpServiceCmHandle(cmHandleID: 'cmhandle-1', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])])
+            dmiRegistration.setUpdatedCmHandles([new NcmpServiceCmHandle(cmHandleID: 'cmhandle-2', publicProperties: ['publicProp1': 'value'], dmiProperties: [:])])
+            dmiRegistration.setRemovedCmHandles(['cmhandle-2'])
+        and: 'update cm-handles can be processed successfully'
+            def updateResponses = [CmHandleRegistrationResponse.createSuccessResponse('cmhandle-2')]
+            mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(*_) >> updateResponses
+        and: 'create cm-handles can be processed successfully'
+            def createdResponses = [CmHandleRegistrationResponse.createSuccessResponse('cmhandle-1')]
+            objectUnderTest.parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(*_) >> createdResponses
+        and: 'delete cm-handles can be processed successfully'
+            def removeResponses = [CmHandleRegistrationResponse.createSuccessResponse('cmhandle-3')]
+            objectUnderTest.parseAndRemoveCmHandlesInDmiRegistration(*_) >> removeResponses
+        when: 'registration is processed'
+            def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration)
+        then: 'response has values from all operations'
+            response.getRemovedCmHandles() == removeResponses
+            response.getCreatedCmHandles() == createdResponses
+            response.getUpdatedCmHandles() == updateResponses
 
-    def 'Register a DMI Plugin for a given cm-handle(s) with JSON processing errors during process.'() {
-        given: 'a registration without cm-handle properties '
-            NetworkCmProxyDataServiceImpl objectUnderTest = getObjectUnderTestWithModelSyncDisabled()
-            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin:'some-plugin')
-            dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle]
-        and: 'an json processing exception occurs'
-            spiedJsonObjectMapper.asJsonString(_) >> { throw (new JsonProcessingException('')) }
-        when: 'registration is updated and modules are synced'
-            objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
-        then: 'a data validation exception is thrown'
-            thrown(DataValidationException)
-    }
 
-    def 'Register a DMI Plugin for the given cm-handle(s) with no data found during delete process.'() {
-        given: 'a registration without cm-handle properties '
-            NetworkCmProxyDataServiceImpl objectUnderTest = getObjectUnderTestWithModelSyncDisabled()
-            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin:'some-plugin')
-            dmiPluginRegistration.removedCmHandles = ['some cm handle']
-        and: 'an json processing exception occurs during delete process'
-            mockCpsDataService.deleteListOrListElement(*_) >>  { throw (new DataNodeNotFoundException('','')) }
-        when: 'registration is updated and modules are synced'
-            objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
-        then: 'no exception is thrown'
-            noExceptionThrown()
     }
 
-    def 'Register a DMI Plugin for the given cm-handle(s) with no schema set found during delete process.'() {
-        given: 'a registration'
-            def objectUnderTest = getObjectUnderTestWithModelSyncDisabled()
-            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin:'my-server')
-            dmiPluginRegistration.removedCmHandles = cmHandlesArray
-        and: 'an exception occurs during delete schema set process'
-            mockCpsModuleService.deleteSchemaSet(_,_,_) >>  { throw (new Exception('')) }
-        when: 'registration is updated and modules are synced'
-            objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
-        then: 'delete list or list element is still called'
-            1 * mockCpsDataService.deleteListOrListElement(_,_,_,_)
-    }
-
-    def 'Dmi plugin registration with #scenario'() {
+    def 'Create CM-handle Validation: Registration with valid Service names: #scenario'() {
         given: 'a registration '
-            def objectUnderTest = getObjectUnderTestWithModelSyncDisabled()
-            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin:dmiPlugin, dmiModelPlugin:dmiModelPlugin,
-                    dmiDataPlugin:dmiDataPlugin)
+            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: dmiPlugin, dmiModelPlugin: dmiModelPlugin,
+                dmiDataPlugin: dmiDataPlugin)
             dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle]
         when: 'update registration and sync module is called with correct DMI plugin information'
             objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
@@ -165,11 +124,10 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
             'data & model using same service' | ''         | 'service1'     | 'service1'
     }
 
-    def 'Invalid DMI plugin registration with #scenario'() {
+    def 'Create CM-handle Validation: Invalid DMI plugin service name with #scenario'() {
         given: 'a registration '
-            def objectUnderTest = getObjectUnderTestWithModelSyncDisabled()
-            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin:dmiPlugin, dmiModelPlugin:dmiModelPlugin,
-                    dmiDataPlugin:dmiDataPlugin)
+            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: dmiPlugin, dmiModelPlugin: dmiModelPlugin,
+                dmiDataPlugin: dmiDataPlugin)
             dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle]
         when: 'registration is called with incorrect DMI plugin information'
             objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
@@ -179,37 +137,251 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
         and: 'registration is not called'
             0 * objectUnderTest.parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(dmiPluginRegistration)
         where:
-            scenario                        | dmiPlugin  | dmiModelPlugin | dmiDataPlugin || expectedMessageDetails
-            'empty DMI plugins'             | ''         | ''             | ''            || 'No DMI plugin service names'
-            'blank DMI plugins'             | ' '        | ' '            | ' '           || 'No DMI plugin service names'
-            'null DMI plugins'              | null       | null           | null          || 'No DMI plugin service names'
-            'all DMI plugins'               | 'service1' | 'service2'     | 'service3'    || 'Cannot register combined plugin service name and other service names'
-            '(combined)DMI and Data Plugin' | 'service1' | ''             | 'service2'    || 'Cannot register combined plugin service name and other service names'
-            '(combined)DMI and model Plugin'| 'service1' | 'service2'     | ''            || 'Cannot register combined plugin service name and other service names'
-            'only model DMI plugin'         | ''         | 'service1'     | ''            || 'Cannot register just a Data or Model plugin service name'
-            'only data DMI plugin'          | ''         | ''             | 'service1'    || 'Cannot register just a Data or Model plugin service name'
+            scenario                         | dmiPlugin  | dmiModelPlugin | dmiDataPlugin || expectedMessageDetails
+            'empty DMI plugins'              | ''         | ''             | ''            || 'No DMI plugin service names'
+            'blank DMI plugins'              | ' '        | ' '            | ' '           || 'No DMI plugin service names'
+            'null DMI plugins'               | null       | null           | null          || 'No DMI plugin service names'
+            'all DMI plugins'                | 'service1' | 'service2'     | 'service3'    || 'Cannot register combined plugin service name and other service names'
+            '(combined)DMI and Data Plugin'  | 'service1' | ''             | 'service2'    || 'Cannot register combined plugin service name and other service names'
+            '(combined)DMI and model Plugin' | 'service1' | 'service2'     | ''            || 'Cannot register combined plugin service name and other service names'
+            'only model DMI plugin'          | ''         | 'service1'     | ''            || 'Cannot register just a Data or Model plugin service name'
+            'only data DMI plugin'           | ''         | ''             | 'service1'    || 'Cannot register just a Data or Model plugin service name'
+    }
+
+    def 'Create CM-Handle Successfully: #scenario.'() {
+        given: 'a registration without cm-handle properties'
+            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server')
+            dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleID: 'cmhandle', dmiProperties: dmiProperties, publicProperties: publicProperties)]
+        when: 'registration is updated'
+            def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+        then: 'a successful response is received'
+            response.getCreatedCmHandles().size() == 1
+            with(response.getCreatedCmHandles().get(0)) {
+                assert it.status == Status.SUCCESS
+                assert it.cmHandle == 'cmhandle'
+            }
+        and: 'save list elements is invoked with the expected parameters'
+            interaction {
+                def expectedJsonData = """{"cm-handles":[{"id":"cmhandle","dmi-service-name":"my-server","additional-properties":$expectedDmiProperties,"public-properties":$expectedPublicProperties}]}"""
+                1 * mockCpsDataService.saveListElements('NCMP-Admin', 'ncmp-dmi-registry',
+                    '/dmi-registry', expectedJsonData, noTimestamp)
+            }
+        then: 'model sync is invoked with expected parameters'
+            1 * objectUnderTest.syncModulesAndCreateAnchor(_) >> { YangModelCmHandle yangModelCmHandle ->
+                {
+                    assert yangModelCmHandle.id == 'cmhandle'
+                    assert yangModelCmHandle.dmiServiceName == 'my-server'
+                    assert spiedJsonObjectMapper.asJsonString(yangModelCmHandle.getPublicProperties()) == expectedPublicProperties
+                    assert spiedJsonObjectMapper.asJsonString(yangModelCmHandle.getDmiProperties()) == expectedDmiProperties
+
+                }
+            }
+        where:
+            scenario                          | dmiProperties            | publicProperties               || expectedDmiProperties                      | expectedPublicProperties
+            'with dmi & public properties'    | ['dmi-key': 'dmi-value'] | ['public-key': 'public-value'] || '[{"name":"dmi-key","value":"dmi-value"}]' | '[{"name":"public-key","value":"public-value"}]'
+            'with only public properties'     | [:]                      | ['public-key': 'public-value'] || '[]'                                       | '[{"name":"public-key","value":"public-value"}]'
+            'with only dmi properties'        | ['dmi-key': 'dmi-value'] | [:]                            || '[{"name":"dmi-key","value":"dmi-value"}]' | '[]'
+            'without dmi & public properties' | [:]                      | [:]                            || '[]'                                       | '[]'
+
     }
 
-    def 'Exception thrown on CM-Handle registration update request'() {
-        given: 'a CM-handle registration'
-            def objectUnderTest = getObjectUnderTestWithModelSyncDisabled()
-        and: 'dmi plugin registration input update request'
-            def dmiPluginReg = new DmiPluginRegistration();
-            dmiPluginReg.dmiPlugin = 'onap.dmap.plugin';
-            dmiPluginReg.updatedCmHandles = [new NcmpServiceCmHandle(cmHandleID: 'unknownHandle')]
-        and: 'update data node leaves is unable to find data node'
-            mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(*_) >> { throw new DataNodeNotFoundException('NCMP-Admin', 'ncmp-dmi-registry') }
-        when: 'update dmi registration is called'
-            objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginReg)
-        then: 'data validation exception is thrown'
-            def exceptionThrown = thrown(DataValidationException.class)
-            assert exceptionThrown.getDetails().contains('DataNode not found')
+    def 'Create CM-Handle Multiple Requests: All cm-handles creation requests are processed'() {
+        given: 'a registration with three cm-handles to be created'
+            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server',
+                createdCmHandles: [new NcmpServiceCmHandle(cmHandleID: 'cmhandle1'),
+                                   new NcmpServiceCmHandle(cmHandleID: 'cmhandle2'),
+                                   new NcmpServiceCmHandle(cmHandleID: 'cmhandle3')])
+        and: 'cm-handle creation is successful for 1st and 3rd; failed for 2nd'
+            mockCpsDataService.saveListElements(_, _, _, _, _) >> {} >> { throw new RuntimeException("Failed") } >> {}
+        when: 'registration is updated to create cm-handles'
+            def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+        then: 'a response is received for all cm-handles'
+            response.getCreatedCmHandles().size() == 3
+        and: '1st and 3rd cm-handle are created successfully'
+            with(response.getCreatedCmHandles().get(0)) {
+                assert it.status == Status.SUCCESS
+                assert it.cmHandle == 'cmhandle1'
+            }
+            with(response.getCreatedCmHandles().get(2)) {
+                assert it.status == Status.SUCCESS
+                assert it.cmHandle == 'cmhandle3'
+            }
+        and: '2nd cm-handle creation fails'
+            with(response.getCreatedCmHandles().get(1)) {
+                assert it.status == Status.FAILURE
+                assert it.registrationError == UNKNOWN_ERROR
+                assert it.errorText == 'Failed'
+                assert it.cmHandle == 'cmhandle2'
+            }
+    }
+
+    def 'Create CM-Handle Error Handling: Registration fails: #scenario'() {
+        given: 'a registration without cm-handle properties'
+            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server')
+            dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleID: 'cmhandle')]
+        and: 'cm-handler registration fails: #scenario'
+            mockCpsDataService.saveListElements(_, _, _, _, _) >> { throw exception }
+        when: 'registration is updated'
+            def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+        then: 'a failure response is received'
+            response.getCreatedCmHandles().size() == 1
+            with(response.getCreatedCmHandles().get(0)) {
+                assert it.status == Status.FAILURE
+                assert it.cmHandle == 'cmhandle'
+                assert it.registrationError == expectedError
+                assert it.errorText == expectedErrorText
+            }
+        and: 'model-sync is not invoked'
+            0 * objectUnderTest.syncModulesAndCreateAnchor(_)
+        where:
+            scenario                                        | exception                                               || expectedError           | expectedErrorText
+            'cm-handle already exist'                       | new AlreadyDefinedException('', new RuntimeException()) || CM_HANDLE_ALREADY_EXIST | 'cm-handle already exists'
+            'unknown exception while registering cm-handle' | new RuntimeException('Failed')                          || UNKNOWN_ERROR           | 'Failed'
+    }
+
+    def 'Create CM-Handle Error Handling: Model Sync fails'() {
+        given: 'objects under test without disabled model sync'
+            def objectUnderTest = getObjectUnderTest()
+        and: 'a registration without cm-handle properties'
+            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server')
+            dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleID: 'cmhandle')]
+        and: 'cm-handler models sync fails'
+            objectUnderTest.syncModulesAndCreateAnchor(*_) >> { throw new RuntimeException('Model-Sync failed') }
+        when: 'registration is updated'
+            def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+        then: 'a failure response is received'
+            response.getCreatedCmHandles().size() == 1
+            with(response.getCreatedCmHandles().get(0)) {
+                assert it.status == Status.FAILURE
+                assert it.cmHandle == 'cmhandle'
+                assert it.registrationError == UNKNOWN_ERROR
+                assert it.errorText == 'Model-Sync failed'
+            }
+        and: 'cm-handle is registered'
+            1 * mockCpsDataService.saveListElements(*_)
+    }
+
+    def 'Update CM-Handle: Update Operation Response is added to the response'() {
+        given: 'a registration to update CmHandles'
+            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server',
+                updatedCmHandles: [{}])
+        and: 'cm-handle updates can be processed successfully'
+            def updateOperationResponse = [CmHandleRegistrationResponse.createSuccessResponse('cm-handle-1'),
+                                           CmHandleRegistrationResponse.createFailureResponse('cm-handle-2', new Exception("Failed")),
+                                           CmHandleRegistrationResponse.createFailureResponse('cm-handle-3', CM_HANDLE_DOES_NOT_EXIST)]
+            mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(_) >> updateOperationResponse
+        when: 'registration is updated'
+            def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+        then: 'the response contains updateOperationResponse'
+            assert response.getUpdatedCmHandles().size() == 3
+            assert response.getUpdatedCmHandles().containsAll(updateOperationResponse)
+    }
+
+    def 'Remove CmHandle Successfully: #scenario'() {
+        given: 'a registration'
+            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server',
+                removedCmHandles: ['cmhandle'])
+        and: '#scenario'
+            mockCpsModuleService.deleteSchemaSet(_, 'cmhandle', CASCADE_DELETE_ALLOWED) >>
+                { if (!schemaSetExist) { throw new SchemaSetNotFoundException("", "") } }
+        when: 'registration is updated to delete cmhandle'
+            def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+        then: 'delete list or list element is called'
+            1 * mockCpsDataService.deleteListOrListElement(_, _, _, _)
+        and: 'successful response is received'
+            assert response.getRemovedCmHandles().size() == 1
+            with(response.getRemovedCmHandles().get(0)) {
+                assert it.status == Status.SUCCESS
+                assert it.cmHandle == 'cmhandle'
+            }
+        where:
+            scenario                                            | schemaSetExist
+            'schema-set exists and can be deleted successfully' | true
+            'schema-set does not exist'                         | false
+    }
+
+    def 'Remove CmHandle: All cm-handles delete requests are processed'() {
+        given: 'a registration with three cm-handles to be deleted'
+            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server',
+                removedCmHandles: ['cmhandle1', 'cmhandle2', 'cmhandle3'])
+        and: 'cm-handle deletion is successful for 1st and 3rd; failed for 2nd'
+            mockCpsDataService.deleteListOrListElement(_, _, _, _) >> {} >> { throw new RuntimeException("Failed") } >> {}
+        when: 'registration is updated to delete cmhandles'
+            def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+        then: 'a response is received for all cm-handles'
+            response.getRemovedCmHandles().size() == 3
+        and: '1st and 3rd cm-handle deletes successfully'
+            with(response.getRemovedCmHandles().get(0)) {
+                assert it.status == Status.SUCCESS
+                assert it.cmHandle == 'cmhandle1'
+            }
+            with(response.getRemovedCmHandles().get(2)) {
+                assert it.status == Status.SUCCESS
+                assert it.cmHandle == 'cmhandle3'
+            }
+        and: '2nd cm-handle deletion fails'
+            with(response.getRemovedCmHandles().get(1)) {
+                assert it.status == Status.FAILURE
+                assert it.registrationError == UNKNOWN_ERROR
+                assert it.errorText == 'Failed'
+                assert it.cmHandle == 'cmhandle2'
+            }
+    }
+
+    def 'Remove CmHandle Error Handling: Schema Set Deletion failed'() {
+        given: 'a registration'
+            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server',
+                removedCmHandles: ['cmhandle'])
+        and: 'schema set deletion failed with unknown error'
+            mockCpsModuleService.deleteSchemaSet(_, _, _) >> { throw new RuntimeException('Failed') }
+        when: 'registration is updated to delete cmhandle'
+            def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+        then: 'no exception is thrown'
+            noExceptionThrown()
+        and: 'cm-handle is not deleted'
+            0 * mockCpsDataService.deleteListOrListElement(_, _, _, _)
+        and: 'a failure response is received'
+            assert response.getRemovedCmHandles().size() == 1
+            with(response.getRemovedCmHandles().get(0)) {
+                assert it.status == Status.FAILURE
+                assert it.cmHandle == 'cmhandle'
+                assert it.errorText == 'Failed'
+                assert it.registrationError == UNKNOWN_ERROR
+            }
+    }
+
+    def 'Remove CmHandle Error Handling: #scenario'() {
+        given: 'a registration'
+            def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server',
+                removedCmHandles: ['cmhandle'])
+        and: 'cm-handle deletion throws exception'
+            mockCpsDataService.deleteListOrListElement(_, _, _, _) >> { throw deleteListElementException }
+        when: 'registration is updated to delete cmhandle'
+            def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
+        then: 'no exception is thrown'
+            noExceptionThrown()
+        and: 'a failure response is received'
+            assert response.getRemovedCmHandles().size() == 1
+            with(response.getRemovedCmHandles().get(0)) {
+                assert it.status == Status.FAILURE
+                assert it.cmHandle == 'cmhandle'
+                assert it.registrationError == expectedError
+                assert it.errorText == expectedErrorText
+            }
+        where:
+            scenario                   | deleteListElementException                | expectedError            | expectedErrorText
+            'cm-handle does not exist' | new DataNodeNotFoundException("", "", "") | CM_HANDLE_DOES_NOT_EXIST | 'cm-handle does not exist'
+            'an unexpected exception'  | new RuntimeException("Failed")            | UNKNOWN_ERROR            | 'Failed'
     }
 
     def getObjectUnderTestWithModelSyncDisabled() {
-        def objectUnderTest = Spy(new NetworkCmProxyDataServiceImpl(mockCpsDataService, spiedJsonObjectMapper, mockDmiDataOperations, mockDmiModelOperations,
-                mockCpsModuleService, mockCpsAdminService, mockNetworkCmProxyDataServicePropertyHandler,mockYangModelCmHandleRetriever))
+        def objectUnderTest = getObjectUnderTest()
         objectUnderTest.syncModulesAndCreateAnchor(*_) >> null
         return objectUnderTest
     }
+
+    def getObjectUnderTest() {
+        return Spy(new NetworkCmProxyDataServiceImpl(mockCpsDataService, spiedJsonObjectMapper, mockDmiDataOperations, mockDmiModelOperations,
+            mockCpsModuleService, mockCpsAdminService, mockNetworkCmProxyDataServicePropertyHandler, mockYangModelCmHandleRetriever))
+    }
 }
index b2a3d77..c21d7e7 100644 (file)
 
 package org.onap.cps.ncmp.api.impl
 
+import org.onap.cps.ncmp.api.impl.exception.InvalidTopicException
 import org.onap.cps.ncmp.api.impl.operations.YangModelCmHandleRetriever
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
+import spock.lang.Shared
 
 import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL
 import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING
@@ -56,6 +58,10 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
     def mockDmiDataOperations = Mock(DmiDataOperations)
     def nullNetworkCmProxyDataServicePropertyHandler = null
     def mockYangModelCmHandleRetriever = Mock(YangModelCmHandleRetriever)
+    def NO_TOPIC = null
+    def NO_REQUEST_ID = null
+    @Shared
+    def OPTIONS_PARAM = '(a=1,b=2)'
 
     def objectUnderTest = new NetworkCmProxyDataServiceImpl(mockCpsDataService, spiedJsonObjectMapper, mockDmiDataOperations, mockDmiModelOperations,
         mockCpsModuleService, mockCpsAdminService, nullNetworkCmProxyDataServicePropertyHandler, mockYangModelCmHandleRetriever)
@@ -64,7 +70,6 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
 
     def dataNode = new DataNode(leaves: ['dmi-service-name': 'testDmiService'])
 
-
     def 'Write resource data for pass-through running from DMI using POST #scenario cm handle properties.'() {
         given: 'cpsDataService returns valid datanode'
             mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
@@ -104,18 +109,21 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
                 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
         and: 'get resource data from DMI is called'
             mockDmiDataOperations.getResourceDataFromDmi(
-                'testCmHandle',
-                'testResourceId',
-                '(a=1,b=2)',
-                'testAcceptParam' ,
-                PASSTHROUGH_OPERATIONAL) >> new ResponseEntity<>('result-json', HttpStatus.OK)
+                    'testCmHandle',
+                    'testResourceId',
+                    OPTIONS_PARAM,
+                    'testAcceptParam',
+                    PASSTHROUGH_OPERATIONAL,
+                    NO_REQUEST_ID,
+                    NO_TOPIC) >> new ResponseEntity<>('dmi-response', HttpStatus.OK)
         when: 'get resource data operational for cm-handle is called'
             def response = objectUnderTest.getResourceDataOperationalForCmHandle('testCmHandle',
-                'testResourceId',
-                'testAcceptParam',
-                '(a=1,b=2)')
+                    'testResourceId',
+                    'testAcceptParam',
+                    OPTIONS_PARAM,
+                    NO_TOPIC)
         then: 'DMI returns a json response'
-            response == 'result-json'
+            response == 'dmi-response'
     }
 
     def 'Get resource data for pass-through operational from DMI with Json Processing Exception.'() {
@@ -129,9 +137,10 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
                 >> new ResponseEntity<>('NOK-json', HttpStatus.NOT_FOUND)
         when: 'get resource data is called'
             objectUnderTest.getResourceDataOperationalForCmHandle('testCmHandle',
-                'testResourceId',
-                'testAcceptParam',
-                '(a=1,b=2)')
+                    'testResourceId',
+                    'testAcceptParam',
+                    OPTIONS_PARAM,
+                    NO_TOPIC)
         then: 'exception is thrown with the expected details'
             def exceptionThrown = thrown(ServerNcmpException.class)
             exceptionThrown.details == 'DMI status code: 404, DMI response body: NOK-json'
@@ -143,16 +152,19 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
                 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
         and: 'DMI returns NOK response'
             mockDmiDataOperations.getResourceDataFromDmi('testCmHandle',
-                'testResourceId',
-                '(a=1,b=2)',
-                'testAcceptParam',
-                PASSTHROUGH_OPERATIONAL)
-                >> new ResponseEntity<>('NOK-json', HttpStatus.NOT_FOUND)
+                    'testResourceId',
+                    OPTIONS_PARAM,
+                    'testAcceptParam',
+                    PASSTHROUGH_OPERATIONAL,
+                    NO_REQUEST_ID,
+                    NO_TOPIC)
+                    >> new ResponseEntity<>('NOK-json', HttpStatus.NOT_FOUND)
         when: 'get resource data is called'
             objectUnderTest.getResourceDataOperationalForCmHandle('testCmHandle',
-                'testResourceId',
-                'testAcceptParam',
-                '(a=1,b=2)')
+                    'testResourceId',
+                    'testAcceptParam',
+                    OPTIONS_PARAM,
+                    NO_TOPIC)
         then: 'exception is thrown'
             def exceptionThrown = thrown(ServerNcmpException.class)
         and: 'details contains the original response'
@@ -165,17 +177,20 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
                 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
         and: 'DMI returns valid response and data'
             mockDmiDataOperations.getResourceDataFromDmi('testCmHandle',
-                'testResourceId',
-                '(a=1,b=2)',
-                'testAcceptParam',
-                PASSTHROUGH_RUNNING) >> new ResponseEntity<>('{result-json}', HttpStatus.OK)
+                    'testResourceId',
+                    OPTIONS_PARAM,
+                    'testAcceptParam',
+                    PASSTHROUGH_RUNNING,
+                    NO_REQUEST_ID,
+                    NO_TOPIC) >> new ResponseEntity<>('{dmi-response}', HttpStatus.OK)
         when: 'get resource data is called'
             def response = objectUnderTest.getResourceDataPassThroughRunningForCmHandle('testCmHandle',
-                'testResourceId',
-                'testAcceptParam',
-                '(a=1,b=2)')
+                    'testResourceId',
+                    'testAcceptParam',
+                    OPTIONS_PARAM,
+                    NO_TOPIC)
         then: 'get resource data returns expected response'
-            response == '{result-json}'
+            response == '{dmi-response}'
     }
 
     def 'Get resource data for pass-through running from DMI return NOK response.'() {
@@ -184,22 +199,91 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
                 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
         and: 'DMI returns NOK response'
             mockDmiDataOperations.getResourceDataFromDmi('testCmHandle',
-                'testResourceId',
-                '(a=1,b=2)',
-                'testAcceptParam',
-                PASSTHROUGH_RUNNING)
-                >> new ResponseEntity<>('NOK-json', HttpStatus.NOT_FOUND)
+                    'testResourceId',
+                    OPTIONS_PARAM,
+                    'testAcceptParam',
+                    PASSTHROUGH_RUNNING,
+                    NO_REQUEST_ID,
+                    NO_TOPIC)
+                    >> new ResponseEntity<>('NOK-json', HttpStatus.NOT_FOUND)
         when: 'get resource data is called'
             objectUnderTest.getResourceDataPassThroughRunningForCmHandle('testCmHandle',
-                'testResourceId',
-                'testAcceptParam',
-                '(a=1,b=2)')
+                    'testResourceId',
+                    'testAcceptParam',
+                    OPTIONS_PARAM,
+                    NO_TOPIC)
         then: 'exception is thrown'
             def exceptionThrown = thrown(ServerNcmpException.class)
         and: 'details contains the original response'
             exceptionThrown.details.contains('NOK-json')
     }
 
+    def 'DMI Operational data request with #scenario'() {
+        given: 'cps data service returns valid data node'
+            mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
+                    cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
+        and: 'dmi data operation returns valid response and data'
+            mockDmiDataOperations.getResourceDataFromDmi(_, _, _, _, _, NO_REQUEST_ID, NO_TOPIC)
+                    >> new ResponseEntity<>('{dmi-response}', HttpStatus.OK)
+        when: 'get resource data is called data operational with blank topic'
+            def responseData = objectUnderTest.getResourceDataOperationalForCmHandle('', '',
+                    '', '', emptyTopic)
+        then: 'a invalid topic exception is thrown'
+            thrown(InvalidTopicException)
+        where: 'the following parameters are used'
+            scenario                               | emptyTopic
+            'no topic value in url'                | ''
+            'empty topic value in url'             | '\"\"'
+            'blank topic value in url'             | ' '
+            'invalid non-empty topic value in url' | '1_5_*_#'
+    }
+
+    def 'Get resource data for data operational from DMI with valid topic i.e. async request.'() {
+        given: 'cps data service returns valid data node'
+            mockCpsDataService.getDataNode(*_) >> dataNode
+        and: 'dmi data operation returns valid response and data'
+            mockDmiDataOperations.getResourceDataFromDmi(_, _, _, _, _, _, 'my-topic-name')
+                    >> new ResponseEntity<>('{dmi-response}', HttpStatus.OK)
+        when: 'get resource data is called for data operational with valid topic'
+            def responseData = objectUnderTest.getResourceDataOperationalForCmHandle('', '', '', '', 'my-topic-name')
+        then: 'non empty request id is generated'
+            assert responseData.body.requestId.length() > 0
+    }
+
+    def 'Get resource data for pass through running from DMI with valid topic async request.'() {
+        given: 'cps data service returns valid data node'
+            mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
+                    cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
+        and: 'dmi data operation returns valid response and data'
+            mockDmiDataOperations.getResourceDataFromDmi(_, _, _, _, _, _, 'my-topic-name')
+                    >> new ResponseEntity<>('{dmi-response}', HttpStatus.OK)
+        when: 'get resource data is called for data operational with valid topic'
+            def responseData = objectUnderTest.getResourceDataPassThroughRunningForCmHandle('',
+                    '', '', OPTIONS_PARAM, 'my-topic-name')
+        then: 'non empty request id is generated'
+            assert responseData.body.requestId.length() > 0
+    }
+
+    def 'DMI pass through running data request with #scenario'() {
+        given: 'cps data service returns valid data node'
+            mockCpsDataService.getDataNode('NCMP-Admin', 'ncmp-dmi-registry',
+                    cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
+        and: 'dmi data operation returns valid response and data'
+            mockDmiDataOperations.getResourceDataFromDmi(_, _, _, _, _, NO_REQUEST_ID, NO_TOPIC)
+                    >> new ResponseEntity<>('{dmi-response}', HttpStatus.OK)
+        when: 'get resource data is called for data operational with valid topic'
+            def responseData = objectUnderTest.getResourceDataPassThroughRunningForCmHandle('',
+                    '', '', '', emptyTopic)
+        then: 'a invalid topic exception is thrown'
+            thrown(InvalidTopicException)
+        where: 'the following parameters are used'
+            scenario                               | emptyTopic
+            'no topic value in url'                | ''
+            'empty topic value in url'             | '\"\"'
+            'blank topic value in url'             | ' '
+            'invalid non-empty topic value in url' | '1_5_*_#'
+    }
+
     def 'Getting Yang Resources.'() {
         when: 'yang resources is called'
             objectUnderTest.getYangResourcesModuleReferences('some cm handle')
@@ -255,7 +339,7 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
                 givenOperation,
                 '{some-json}',
                 'application/json')
-        then: 'an exception is thrown with the expected error message detailsd with correct operation'
+        then: 'an exception is thrown with the expected error message details with correct operation'
             def exceptionThrown = thrown(ServerNcmpException.class)
             exceptionThrown.getMessage().contains(expectedResponseMessage)
         where:
index 9b8d4ad..f6264f4 100644 (file)
@@ -1,6 +1,7 @@
 /*
  * ============LICENSE_START=======================================================
  * Copyright (C) 2022 Nordix Foundation
+ * Modifications Copyright (C) 2022 Bell Canada
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
 
 package org.onap.cps.ncmp.api.impl
 
+import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.CM_HANDLE_DOES_NOT_EXIST
+import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError.UNKNOWN_ERROR
+import static org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status
+
 import org.onap.cps.api.CpsDataService
+import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse
 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
 import org.onap.cps.spi.FetchDescendantsOption
 import org.onap.cps.spi.exceptions.DataNodeNotFoundException
-import org.onap.cps.spi.exceptions.DataValidationException
 import org.onap.cps.spi.model.DataNode
 import org.onap.cps.spi.model.DataNodeBuilder
 import spock.lang.Specification
@@ -117,12 +122,53 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification {
         given: 'cm handles request'
             def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleID: cmHandleId, publicProperties: [:], dmiProperties: [:])]
         and: 'data node cannot be found'
-            mockCpsDataService.getDataNode(*_) >> { throw new DataNodeNotFoundException(dataspaceName, anchorName, cmHandleXpath) }
+            mockCpsDataService.getDataNode(*_) >> { throw exception }
         when: 'update data node leaves is called using correct parameters'
-            objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest)
-        then: 'data validation exception is thrown'
-            def exceptionThrown = thrown(DataValidationException.class)
-            assert exceptionThrown.getMessage().contains('DataNode not found')
+            def response = objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest)
+        then: 'one failed registration response'
+            response.size() == 1
+        and: 'it has expected error details'
+            with(response.get(0)) {
+                assert it.status == Status.FAILURE
+                assert it.cmHandle == cmHandleId
+                assert it.registrationError == expectedError
+                assert it.errorText == expectedErrorText
+            }
+        where:
+            scenario                  | exception                                                        || expectedError            | expectedErrorText
+            'cmhandle does not exist' | new DataNodeNotFoundException('NCMP-Admin', 'ncmp-dmi-registry') || CM_HANDLE_DOES_NOT_EXIST | 'cm-handle does not exist'
+            'unexpected error'        | new RuntimeException('Failed')                                   || UNKNOWN_ERROR            | 'Failed'
+    }
+
+    def 'Multiple update operations in a single request'() {
+        given: 'cm handles request'
+            def cmHandleUpdateRequest = [new NcmpServiceCmHandle(cmHandleID: cmHandleId, publicProperties: ['publicProp1': "value"], dmiProperties: [:]),
+                                         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'
+            mockCpsDataService.getDataNode(*_) >> cmHandleDataNode >> { throw new DataNodeNotFoundException('NCMP-Admin', 'ncmp-dmi-registry') } >> cmHandleDataNode
+        when: 'update data node leaves is called using correct parameters'
+            def cmHandleResponseList = objectUnderTest.updateCmHandleProperties(cmHandleUpdateRequest)
+        then: 'response has 3 values'
+            cmHandleResponseList.size() == 3
+        and: 'the 1st and 3rd requests were processed successfully'
+            with(cmHandleResponseList.get(0)) {
+                assert it.status == Status.SUCCESS
+                assert it.cmHandle == cmHandleId
+            }
+            with(cmHandleResponseList.get(2)) {
+                assert it.status == Status.SUCCESS
+                assert it.cmHandle == cmHandleId
+            }
+        and: 'the 2nd request failed with correct error code'
+            with(cmHandleResponseList.get(1)) {
+                assert it.status == Status.FAILURE
+                assert it.cmHandle == cmHandleId
+                assert it.registrationError == CM_HANDLE_DOES_NOT_EXIST
+                assert it.errorText == "cm-handle does not exist"
+            }
+        then: 'the replace list method is called twice'
+            2 * mockCpsDataService.replaceListContent(*_)
     }
 
     def convertToProperties(expectedPropertiesAfterUpdateAsMap) {
@@ -133,4 +179,5 @@ class NetworkCmProxyDataServicePropertyHandlerSpec extends Specification {
             }))
         return properties
     }
+
 }
index e585825..3df862a 100644 (file)
@@ -22,12 +22,15 @@ package org.onap.cps.ncmp.api.impl.operations
 
 import com.fasterxml.jackson.databind.ObjectMapper
 import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration
+import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder
 import org.onap.cps.utils.JsonObjectMapper
 import org.spockframework.spring.SpringBean
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.boot.test.context.SpringBootTest
 import org.springframework.http.ResponseEntity
 import org.springframework.test.context.ContextConfiguration
+import org.springframework.util.MultiValueMap
+import spock.lang.Shared
 
 import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL
 import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING
@@ -40,43 +43,54 @@ import org.springframework.http.HttpStatus
 class DmiDataOperationsSpec extends DmiOperationsBaseSpec {
 
     @SpringBean
-    JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
+    DmiServiceUrlBuilder dmiServiceUrlBuilder = Mock()
+    def dmiServiceBaseUrl = "${dmiServiceName}/dmi/v1/ch/${cmHandleId}/data/ds/ncmp-datastore:"
+    def NO_TOPIC = null
+    def NO_REQUEST_ID = null
+    @Shared
+    def OPTIONS_PARAM = '(a=1,b=2)'
+
+    @SpringBean
+    JsonObjectMapper spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper()))
 
     @Autowired
     DmiDataOperations objectUnderTest
 
-    def 'call get resource data for #expectedDatastoreInUrl from DMI #scenario.'() {
+    def 'call get resource data for #expectedDatastoreInUrl from DMI without topic #scenario.'() {
         given: 'a cm handle for #cmHandleId'
             mockYangModelCmHandleRetrieval(dmiProperties)
         and: 'a positive response from DMI service when it is called with the expected parameters'
             def responseFromDmi = new ResponseEntity<Object>(HttpStatus.OK)
-            mockDmiRestClient.postOperationWithJsonData(
-                "${dmiServiceName}/dmi/v1/ch/${cmHandleId}/data/ds/ncmp-datastore:${expectedDatastoreInUrl}?resourceIdentifier=${resourceIdentifier}${expectedOptionsInUrl}",
-                expectedJson, [Accept:['sample accept header']]) >> responseFromDmi
+            def expectedUrl = dmiServiceBaseUrl + "${expectedDatastoreInUrl}?resourceIdentifier=${resourceIdentifier}${expectedOptionsInUrl}"
+            mockDmiRestClient.postOperationWithJsonData(expectedUrl,
+                    expectedJson, [Accept: ['sample accept header']]) >> responseFromDmi
+            dmiServiceUrlBuilder.getDmiDatastoreUrl(_, _) >> expectedUrl
         when: 'get resource data is invoked'
-            def result = objectUnderTest.getResourceDataFromDmi(cmHandleId,resourceIdentifier, options,'sample accept header', dataStore)
+            def result = objectUnderTest.getResourceDataFromDmi(cmHandleId, resourceIdentifier,
+                    options, 'sample accept header', dataStore, NO_REQUEST_ID, NO_TOPIC)
         then: 'the result is the response from the DMI service'
             assert result == responseFromDmi
         where: 'the following parameters are used'
-            scenario             | dmiProperties        | dataStore               | options     || expectedJson                                                 | expectedDatastoreInUrl    | expectedOptionsInUrl
-            'without properties' | []                   | PASSTHROUGH_OPERATIONAL | '(a=1,b=2)' || '{"operation":"read","cmHandleProperties":{}}'               | 'passthrough-operational' | '&options=(a=1,b=2)'
-            'with properties'    | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | '(a=1,b=2)' || '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}' | 'passthrough-operational' | '&options=(a=1,b=2)'
-            'null options'       | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | null        || '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}' | 'passthrough-operational' | ''
-            'empty options'      | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | ''          || '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}' | 'passthrough-operational' | ''
-            'datastore running'  | []                   | PASSTHROUGH_RUNNING     | '(a=1,b=2)' || '{"operation":"read","cmHandleProperties":{}}'               | 'passthrough-running'     | '&options=(a=1,b=2)'
+            scenario                               | dmiProperties               | dataStore               | options       || expectedJson                                                 | expectedDatastoreInUrl    | expectedOptionsInUrl
+            'without properties'                   | []                          | PASSTHROUGH_OPERATIONAL | OPTIONS_PARAM || '{"operation":"read","cmHandleProperties":{}}'               | 'passthrough-operational' | '&options=(a=1,b=2)'
+            'with properties'                      | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | OPTIONS_PARAM || '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}' | 'passthrough-operational' | '&options=(a=1,b=2)'
+            'null options'                         | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | null          || '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}' | 'passthrough-operational' | ''
+            'empty options'                        | [yangModelCmHandleProperty] | PASSTHROUGH_OPERATIONAL | ''            || '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}' | 'passthrough-operational' | ''
+            'datastore running without properties' | []                          | PASSTHROUGH_RUNNING     | OPTIONS_PARAM || '{"operation":"read","cmHandleProperties":{}}'               | 'passthrough-running'     | '&options=(a=1,b=2)'
+            'datastore running with properties'    | [yangModelCmHandleProperty] | PASSTHROUGH_RUNNING     | OPTIONS_PARAM || '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}' | 'passthrough-running'     | '&options=(a=1,b=2)'
     }
 
     def 'Write data for pass-through:running datastore in DMI.'() {
         given: 'a cm handle for #cmHandleId'
             mockYangModelCmHandleRetrieval([yangModelCmHandleProperty])
         and: 'a positive response from DMI service when it is called with the expected parameters'
-            def expectedUrl = "${dmiServiceName}/dmi/v1/ch/${cmHandleId}/data/ds" +
-                "/ncmp-datastore:passthrough-running?resourceIdentifier=${resourceIdentifier}"
+            def expectedUrl = dmiServiceBaseUrl + "passthrough-running?resourceIdentifier=${resourceIdentifier}"
             def expectedJson = '{"operation":"' + expectedOperationInUrl + '","dataType":"some data type","data":"requestData","cmHandleProperties":{"prop1":"val1"}}'
             def responseFromDmi = new ResponseEntity<Object>(HttpStatus.OK)
+            dmiServiceUrlBuilder.getDmiDatastoreUrl(_, _) >> expectedUrl
             mockDmiRestClient.postOperationWithJsonData(expectedUrl, expectedJson, [:]) >> responseFromDmi
         when: 'write resource method is invoked'
-            def result = objectUnderTest.writeResourceDataPassThroughRunningFromDmi(cmHandleId,'parent/child', operation, 'requestData', 'some data type')
+            def result = objectUnderTest.writeResourceDataPassThroughRunningFromDmi(cmHandleId, 'parent/child', operation, 'requestData', 'some data type')
         then: 'the result is the response from the DMI service'
             assert result == responseFromDmi
         where: 'the following operation is performed'
@@ -84,5 +98,4 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec {
             CREATE    || 'create'
             UPDATE    || 'update'
     }
-
 }
\ No newline at end of file
index cd2cb71..d3fc17c 100644 (file)
@@ -23,6 +23,7 @@ package org.onap.cps.ncmp.api.impl.operations
 import com.fasterxml.jackson.core.JsonProcessingException
 import com.fasterxml.jackson.databind.ObjectMapper
 import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration
+import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder
 import org.onap.cps.spi.model.ModuleReference
 import org.onap.cps.utils.JsonObjectMapper
 import org.spockframework.spring.SpringBean
@@ -31,6 +32,7 @@ import org.springframework.boot.test.context.SpringBootTest
 import org.springframework.http.HttpStatus
 import org.springframework.http.ResponseEntity
 import org.springframework.test.context.ContextConfiguration
+import org.springframework.web.util.UriComponentsBuilder
 import spock.lang.Shared
 
 @SpringBootTest
@@ -50,14 +52,15 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec {
         given: 'a cm handle'
             mockYangModelCmHandleRetrieval([])
         and: 'a positive response from DMI service when it is called with the expected parameters'
-            def moduleReferencesAsLisOfMaps = [[moduleName:'mod1',revision:'A'],[moduleName:'mod2',revision:'X']]
-            def responseFromDmi = new ResponseEntity([schemas:moduleReferencesAsLisOfMaps], HttpStatus.OK)
-            mockDmiRestClient.postOperationWithJsonData("${dmiServiceName}/dmi/v1/ch/${cmHandleId}/modules",
-                '{"cmHandleProperties":{}}', [:]) >> responseFromDmi
+            def moduleReferencesAsLisOfMaps = [[moduleName: 'mod1', revision: 'A'], [moduleName: 'mod2', revision: 'X']]
+            def expectedUrl = "${dmiServiceName}/dmi/v1/ch/${cmHandleId}/modules"
+            def responseFromDmi = new ResponseEntity([schemas: moduleReferencesAsLisOfMaps], HttpStatus.OK)
+            mockDmiRestClient.postOperationWithJsonData(expectedUrl, '{"cmHandleProperties":{}}', [:])
+                    >> responseFromDmi
         when: 'get module references is called'
             def result = objectUnderTest.getModuleReferences(yangModelCmHandle)
         then: 'the result consists of expected module references'
-            assert result == [new ModuleReference(moduleName:'mod1',revision:'A'), new ModuleReference(moduleName:'mod2',revision:'X')]
+            assert result == [new ModuleReference(moduleName: 'mod1', revision: 'A'), new ModuleReference(moduleName: 'mod2', revision: 'X')]
     }
 
     def 'Retrieving module references edge case: #scenario.'() {
index dd0d64d..e6f63ce 100644 (file)
@@ -22,7 +22,9 @@ package org.onap.cps.ncmp.api.impl.operations
 
 import com.fasterxml.jackson.databind.ObjectMapper
 import org.onap.cps.ncmp.api.impl.client.DmiRestClient
+import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
+import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder
 import org.spockframework.spring.SpringBean
 import spock.lang.Shared
 import spock.lang.Specification
@@ -41,6 +43,9 @@ abstract class DmiOperationsBaseSpec extends Specification {
     @SpringBean
     ObjectMapper spyObjectMapper = Spy()
 
+    @SpringBean
+    DmiServiceUrlBuilder dmiServiceUrlBuilder = new DmiServiceUrlBuilder(new NcmpConfiguration.DmiProperties())
+
     def yangModelCmHandle = new YangModelCmHandle()
     def static dmiServiceName = 'some service name'
     def static cmHandleId = 'some cm handle'
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponseSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponseSpec.groovy
new file mode 100644 (file)
index 0000000..c5ef2f4
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2022 Bell Canada
+ *  ================================================================================
+ *  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.models
+
+import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationError
+import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status
+import spock.lang.Specification
+
+class CmHandleRegistrationResponseSpec extends Specification {
+
+    def 'Successful CmHandle Registration Response'() {
+        when: 'CMHandle response is created'
+            def cmHandleRegistrationResponse = CmHandleRegistrationResponse.createSuccessResponse('cmHandle')
+        then: 'a success response is returned'
+            with(cmHandleRegistrationResponse) {
+                assert it.cmHandle == 'cmHandle'
+                assert it.status == Status.SUCCESS
+            }
+        and: 'error details are null'
+            cmHandleRegistrationResponse.registrationError == null
+            cmHandleRegistrationResponse.errorText == null
+    }
+
+    def 'Failed Cm Handle Registration Response: for unexpected exception'() {
+        when: 'CMHandle response is created for an unexpected exception'
+            def cmHandleRegistrationResponse =
+                CmHandleRegistrationResponse.createFailureResponse('cmHandle', new Exception('unexpected error'))
+        then: 'the response is created with expected value'
+            with(cmHandleRegistrationResponse) {
+                assert it.registrationError == RegistrationError.UNKNOWN_ERROR
+                assert it.cmHandle == 'cmHandle'
+                assert errorText == 'unexpected error'
+            }
+    }
+
+    def 'Failed Cm Handle Registration Response: for known error'() {
+        when: 'CMHandle response is created for known error'
+            def cmHandleRegistrationResponse =
+                CmHandleRegistrationResponse.createFailureResponse('cmHandle', RegistrationError.CM_HANDLE_ALREADY_EXIST)
+        then: 'the response is created with expected value'
+            with(cmHandleRegistrationResponse) {
+                assert it.registrationError == RegistrationError.CM_HANDLE_ALREADY_EXIST
+                assert it.cmHandle == 'cmHandle'
+                assert it.status == Status.FAILURE
+                assert errorText == RegistrationError.CM_HANDLE_ALREADY_EXIST.errorText
+            }
+
+    }
+
+}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/moduleReferenceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/moduleReferenceSpec.groovy
deleted file mode 100644 (file)
index 444a258..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- *  Copyright (C) 2021 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.models
-
-import org.onap.cps.spi.model.ExtendedModuleReference
-import spock.lang.Specification
-
-class moduleReferenceSpec extends Specification {
-
-    def 'lombok data annotation correctly implements toString() and hashCode() methods'() {
-        given: 'two moduleReference objects'
-            def moduleReference1 = new ExtendedModuleReference('module1', "some namespace", '1')
-            def moduleReference2 = new ExtendedModuleReference('module1', "some namespace", '1')
-        when: 'lombok generated methods are called'
-        then: 'the methods exist and behaviour is accurate'
-            assert moduleReference1.toString() == moduleReference2.toString()
-            assert moduleReference1.hashCode() == moduleReference2.hashCode()
-        and: 'therefore equals works as expected'
-            assert moduleReference1.equals(moduleReference2)
-    }
-
-}
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/utils/DmiServiceUrlBuilderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/utils/DmiServiceUrlBuilderSpec.groovy
new file mode 100644 (file)
index 0000000..1615d05
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ *  ============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.utils
+
+import static org.onap.cps.ncmp.api.impl.operations.DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING
+
+import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
+import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration
+import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder
+import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
+import spock.lang.Shared
+import spock.lang.Specification
+
+class DmiServiceUrlBuilderSpec extends Specification {
+
+    @Shared
+    YangModelCmHandle yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle("dmiServiceName",
+            "dmiDataServiceName", "dmiModuleServiceName", new NcmpServiceCmHandle())
+
+    NcmpConfiguration.DmiProperties dmiProperties = new NcmpConfiguration.DmiProperties();
+
+    def objectUnderTest = new DmiServiceUrlBuilder(dmiProperties)
+
+    def 'Create the dmi service url with #scenario.'() {
+        given: 'uri variables'
+            dmiProperties.dmiBasePath = 'dmi';
+            def uriVars = objectUnderTest.populateUriVariables(yangModelCmHandle,
+                    "cmHandle", PASSTHROUGH_RUNNING);
+        and: 'query params'
+            def uriQueries = objectUnderTest.populateQueryParams(resourceId,
+                    'optionsParamInQuery', topicParamInQuery);
+        when: 'a dmi datastore service url is generated'
+            def dmiServiceUrl = objectUnderTest.getDmiDatastoreUrl(uriQueries, uriVars)
+        then: 'service url is generated as expected'
+            assert dmiServiceUrl == expectedDmiServiceUrl
+        where: 'the following parameters are used'
+            scenario                       | topicParamInQuery   | resourceId   || expectedDmiServiceUrl
+            'With valid resourceId'        | 'topicParamInQuery' | 'resourceId' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=resourceId&options=optionsParamInQuery&topic=topicParamInQuery'
+            'With Empty resourceId'        | 'topicParamInQuery' | ''           || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running?options=optionsParamInQuery&topic=topicParamInQuery'
+            'With Empty dmi base path'     | 'topicParamInQuery' | 'resourceId' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=resourceId&options=optionsParamInQuery&topic=topicParamInQuery'
+            'With Empty topicParamInQuery' | ''                  | 'resourceId' || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running?resourceIdentifier=resourceId&options=optionsParamInQuery'
+    }
+
+    def 'Populate dmi data store url #scenario.'() {
+        given: 'uri variables are created'
+            dmiProperties.dmiBasePath = dmiBasePath;
+            def uriVars = objectUnderTest.populateUriVariables(yangModelCmHandle,
+                    "cmHandle", PASSTHROUGH_RUNNING);
+        and: 'null query params'
+            def uriQueries = objectUnderTest.populateQueryParams(null,
+                    null, null);
+        when: 'a dmi datastore service url is generated'
+            def dmiServiceUrl = objectUnderTest.getDmiDatastoreUrl(uriQueries, uriVars)
+        then: 'the created dmi service url matches the expected'
+            assert dmiServiceUrl == expectedDmiServiceUrl
+        where: 'the following parameters are used'
+            scenario               | decription                                | dmiBasePath || expectedDmiServiceUrl
+            'with base path  / '   | 'Invalid base path as it starts with /'   | '/dmi'      || 'dmiServiceName//dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running'
+            'without base path / ' | 'Valid path as it does not starts with /' | 'dmi'       || 'dmiServiceName/dmi/v1/ch/cmHandle/data/ds/ncmp-datastore:passthrough-running'
+    }
+}
index d8fbb64..c23926e 100644 (file)
@@ -1,5 +1,5 @@
 #  ============LICENSE_START=======================================================
-#  Copyright (C) 2021 Nordix Foundation
+#  Copyright (C) 2021-2022 Nordix Foundation
 #  ================================================================================
 #  Licensed under the Apache License, Version 2.0 (the "License");
 #  you may not use this file except in compliance with the License.
@@ -21,5 +21,5 @@ dmi:
         username: some-user
         password: some-password
     api:
-        base-path: /dmi
+        base-path: dmi
 
index e03dce3..b76c63c 100755 (executable)
@@ -32,7 +32,7 @@
 
     <groupId>org.onap.cps</groupId>
     <artifactId>cps-parent</artifactId>
-    <version>3.0.0-SNAPSHOT</version>
+    <version>3.1.0-SNAPSHOT</version>
     <packaging>pom</packaging>
 
     <properties>
                 <plugin>
                     <groupId>org.springframework.boot</groupId>
                     <artifactId>spring-boot-maven-plugin</artifactId>
-                    <version>2.3.3.RELEASE</version>
+                    <version>2.6.4</version>
                     <executions>
                         <execution>
                             <goals>
index c8b88e8..514784c 100644 (file)
@@ -23,7 +23,7 @@
     <parent>
         <groupId>org.onap.cps</groupId>
         <artifactId>cps-parent</artifactId>
-        <version>3.0.0-SNAPSHOT</version>
+        <version>3.1.0-SNAPSHOT</version>
         <relativePath>../cps-parent/pom.xml</relativePath>
     </parent>
 
index ae0326d..269e724 100644 (file)
@@ -73,6 +73,8 @@ components:
     SchemaSetDetails:
       type: object
       title: Schema set details by dataspace and schemasetName
+      required:
+        - "moduleReferences"
       properties:
         dataspaceName:
           type: string
index 20870c3..6019197 100755 (executable)
@@ -2,6 +2,7 @@
 <!--
   ============LICENSE_START=======================================================
   Copyright (c) 2020 Linux Foundation.
+  Modifications Copyright (C) 2020-2022 Nordix Foundation.
   Modifications Copyright (C) 2021 Bell Canada.
   ================================================================================
   Licensed under the Apache License, Version 2.0 (the "License");
@@ -27,7 +28,7 @@
     <parent>
         <groupId>org.onap.cps</groupId>
         <artifactId>cps-parent</artifactId>
-        <version>3.0.0-SNAPSHOT</version>
+        <version>3.1.0-SNAPSHOT</version>
         <relativePath>../cps-parent/pom.xml</relativePath>
     </parent>
 
             <artifactId>commons-lang3</artifactId>
         </dependency>
         <dependency>
-            <groupId>org.modelmapper</groupId>
-            <artifactId>modelmapper</artifactId>
+            <groupId>org.mapstruct</groupId>
+            <artifactId>mapstruct</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mapstruct</groupId>
+            <artifactId>mapstruct-processor</artifactId>
         </dependency>
         <!-- T E S T   D E P E N D E N C I E S -->
         <dependency>
diff --git a/cps-rest/src/main/java/org/onap/cps/config/CpsConfig.java b/cps-rest/src/main/java/org/onap/cps/config/CpsConfig.java
deleted file mode 100755 (executable)
index 4f4501a..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-/*\r
- * ============LICENSE_START=======================================================\r
- * Copyright (C) 2020 Nordix Foundation.\r
- * Modifications Copyright (C) 2021 Pantheon.tech\r
- * Modifications Copyright (C) 2021 Bell Canada.\r
- * ================================================================================\r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- *      http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- *\r
- * SPDX-License-Identifier: Apache-2.0\r
- * ============LICENSE_END=========================================================\r
- */\r
-\r
-package org.onap.cps.config;\r
-\r
-import org.modelmapper.ModelMapper;\r
-import org.springframework.context.annotation.Bean;\r
-import org.springframework.context.annotation.Configuration;\r
-import org.springframework.retry.annotation.EnableRetry;\r
-\r
-@Configuration\r
-@EnableRetry\r
-public class CpsConfig {\r
-\r
-    /**\r
-     * ModelMapper configuration.\r
-     */\r
-    @Bean\r
-    public ModelMapper modelMapper() {\r
-        return new ModelMapper();\r
-    }\r
-}\r
index 52e64a9..2707d9f 100755 (executable)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2020 Nordix Foundation
+ *  Copyright (C) 2020-2022 Nordix Foundation
  *  Modifications Copyright (C) 2020-2021 Bell Canada.
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  ================================================================================
@@ -30,7 +30,7 @@ import java.util.List;
 import java.util.stream.Collectors;
 import javax.validation.Valid;
 import javax.validation.constraints.NotNull;
-import org.modelmapper.ModelMapper;
+import lombok.RequiredArgsConstructor;
 import org.onap.cps.api.CpsAdminService;
 import org.onap.cps.api.CpsModuleService;
 import org.onap.cps.rest.api.CpsAdminApi;
@@ -38,7 +38,6 @@ import org.onap.cps.rest.model.AnchorDetails;
 import org.onap.cps.rest.model.SchemaSetDetails;
 import org.onap.cps.spi.model.Anchor;
 import org.onap.cps.spi.model.SchemaSet;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -47,16 +46,12 @@ import org.springframework.web.multipart.MultipartFile;
 
 @RestController
 @RequestMapping("${rest.api.cps-base-path}")
+@RequiredArgsConstructor
 public class AdminRestController implements CpsAdminApi {
 
-    @Autowired
-    private CpsAdminService cpsAdminService;
-
-    @Autowired
-    private CpsModuleService cpsModuleService;
-
-    @Autowired
-    private ModelMapper modelMapper;
+    private final CpsAdminService cpsAdminService;
+    private final CpsModuleService cpsModuleService;
+    private final CpsRestInputMapper cpsRestInputMapper;
 
     /**
      * Create a dataspace.
@@ -107,7 +102,7 @@ public class AdminRestController implements CpsAdminApi {
     @Override
     public ResponseEntity<SchemaSetDetails> getSchemaSet(final String dataspaceName, final String schemaSetName) {
         final var schemaSet = cpsModuleService.getSchemaSet(dataspaceName, schemaSetName);
-        final var schemaSetDetails = modelMapper.map(schemaSet, SchemaSetDetails.class);
+        final var schemaSetDetails = cpsRestInputMapper.toSchemaSetDetails(schemaSet);
         return new ResponseEntity<>(schemaSetDetails, HttpStatus.OK);
     }
 
@@ -162,7 +157,7 @@ public class AdminRestController implements CpsAdminApi {
     @Override
     public ResponseEntity<AnchorDetails> getAnchor(final String dataspaceName, final String anchorName) {
         final var anchor = cpsAdminService.getAnchor(dataspaceName, anchorName);
-        final var anchorDetails = modelMapper.map(anchor, AnchorDetails.class);
+        final var anchorDetails = cpsRestInputMapper.toAnchorDetails(anchor);
         return new ResponseEntity<>(anchorDetails, HttpStatus.OK);
     }
 
@@ -175,8 +170,8 @@ public class AdminRestController implements CpsAdminApi {
     @Override
     public ResponseEntity<List<AnchorDetails>> getAnchors(final String dataspaceName) {
         final Collection<Anchor> anchors = cpsAdminService.getAnchors(dataspaceName);
-        final List<AnchorDetails> anchorDetails = anchors.stream().map(anchor ->
-            modelMapper.map(anchor, AnchorDetails.class)).collect(Collectors.toList());
+        final List<AnchorDetails> anchorDetails = anchors.stream().map(cpsRestInputMapper::toAnchorDetails)
+            .collect(Collectors.toList());
         return new ResponseEntity<>(anchorDetails, HttpStatus.OK);
     }
 }
diff --git a/cps-rest/src/main/java/org/onap/cps/rest/controller/CpsRestInputMapper.java b/cps-rest/src/main/java/org/onap/cps/rest/controller/CpsRestInputMapper.java
new file mode 100644 (file)
index 0000000..d0a4a10
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ *  ============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.rest.controller;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.NullValueCheckStrategy;
+import org.mapstruct.NullValuePropertyMappingStrategy;
+import org.onap.cps.rest.model.AnchorDetails;
+import org.onap.cps.rest.model.SchemaSetDetails;
+import org.onap.cps.spi.model.Anchor;
+import org.onap.cps.spi.model.SchemaSet;
+
+@Mapper(componentModel = "spring")
+public interface CpsRestInputMapper {
+
+    @Mapping(source = "moduleReferences", target = "moduleReferences",
+        nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS,
+        nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT)
+    SchemaSetDetails toSchemaSetDetails(final SchemaSet schemaSet);
+
+    AnchorDetails toAnchorDetails(final Anchor anchor);
+
+}
index e8cfcfb..58a5ebf 100755 (executable)
@@ -2,7 +2,7 @@
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2020-2021 Pantheon.tech
  *  Modifications Copyright (C) 2020-2021 Bell Canada.
- *  Modifications Copyright (C) 2021 Nordix Foundation
+ *  Modifications Copyright (C) 2021-2022 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
 
 package org.onap.cps.rest.controller
 
+import org.mapstruct.factory.Mappers
+
 import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_PROHIBITED
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
 
-import org.modelmapper.ModelMapper
 import org.onap.cps.api.CpsAdminService
-import org.onap.cps.api.CpsDataService
 import org.onap.cps.api.CpsModuleService
-import org.onap.cps.api.CpsQueryService
 import org.onap.cps.spi.exceptions.AlreadyDefinedException
 import org.onap.cps.spi.exceptions.SchemaSetInUseException
 import org.onap.cps.spi.model.Anchor
@@ -59,7 +58,7 @@ class AdminRestControllerSpec extends Specification {
     CpsAdminService mockCpsAdminService = Mock()
 
     @SpringBean
-    ModelMapper modelMapper = Spy()
+    CpsRestInputMapper cpsRestInputMapper = Mappers.getMapper(CpsRestInputMapper)
 
     @Autowired
     MockMvc mvc
@@ -68,10 +67,9 @@ class AdminRestControllerSpec extends Specification {
     def basePath
 
     def dataspaceName = 'my_dataspace'
-    def anchor = new Anchor(name: 'my_anchor')
-    def anchorList = [anchor]
     def anchorName = 'my_anchor'
     def schemaSetName = 'my_schema_set'
+    def anchor = new Anchor(name: anchorName, dataspaceName: dataspaceName, schemaSetName: schemaSetName)
 
     def 'Create new dataspace.'() {
         given: 'an endpoint'
@@ -274,7 +272,7 @@ class AdminRestControllerSpec extends Specification {
 
     def 'Get existing anchor.'() {
         given: 'service method returns a list of anchors'
-            mockCpsAdminService.getAnchors(dataspaceName) >> anchorList
+            mockCpsAdminService.getAnchors(dataspaceName) >> [anchor]
         and: 'an endpoint'
             def anchorEndpoint = "$basePath/v1/dataspaces/$dataspaceName/anchors"
         when: 'get all anchors API is invoked'
diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/CpsRestInputMapperSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/CpsRestInputMapperSpec.groovy
new file mode 100644 (file)
index 0000000..9ff1a9f
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ *  ============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.rest.controller
+
+import org.mapstruct.factory.Mappers
+import org.onap.cps.rest.model.AnchorDetails
+import org.onap.cps.rest.model.SchemaSetDetails
+import org.onap.cps.spi.model.Anchor
+import org.onap.cps.rest.model.ModuleReferences
+import org.onap.cps.spi.model.ModuleReference
+import org.onap.cps.spi.model.SchemaSet
+import spock.lang.Specification
+
+class CpsRestInputMapperSpec extends Specification {
+
+    def objectUnderTest = Mappers.getMapper(CpsRestInputMapper.class)
+
+    def 'Convert a SchemaSet to a SchemaSetDetails a ModuleReference'() {
+        given: 'a ModuleReference'
+            def moduleReference = new ModuleReference()
+        and: 'a SchemaSet containing the ModuleReference'
+            def schemaSet = new SchemaSet(name: 'some-schema-set', dataspaceName: 'some-dataspace',
+                moduleReferences: [moduleReference])
+        when: 'to schemaSetDetails is called'
+            def result = objectUnderTest.toSchemaSetDetails(schemaSet)
+        then: 'the result returns a SchemaSetDetails'
+            result.class == SchemaSetDetails.class
+        and: 'the results ModuleReferences are of type ModuleReference'
+            result.moduleReferences[0].class == ModuleReferences.class
+    }
+
+    def 'Convert a schemaSet to a SchemaSetDetails without an ModuleReference'() {
+        given: 'a SchemaSet'
+            def schemaSet = new SchemaSet()
+        when: 'to schemaSetDetails is called'
+            def result = objectUnderTest.toSchemaSetDetails(schemaSet)
+        then: 'the result returns a SchemaSetDetails'
+            result.class == SchemaSetDetails.class
+        and: 'the ModuleReferences of SchemaSetDetails is an empty collection'
+            result.moduleReferences.size() == 0
+    }
+
+    def 'Convert an Anchor to an AnchorDetails'() {
+        given: 'an Anchor'
+            def anchor = new Anchor()
+        when: 'to anchorDetails is called'
+            def result = objectUnderTest.toAnchorDetails(anchor)
+        then: 'the result returns an AnchorDetails'
+            result.class == AnchorDetails.class
+    }
+}
index a2eaa52..2aa4ddd 100644 (file)
 
 package org.onap.cps.rest.exceptions
 
+import com.fasterxml.jackson.databind.ObjectMapper
 import groovy.json.JsonSlurper
-import org.modelmapper.ModelMapper
+import org.mapstruct.factory.Mappers
 import org.onap.cps.api.CpsAdminService
 import org.onap.cps.api.CpsDataService
 import org.onap.cps.api.CpsModuleService
 import org.onap.cps.api.CpsQueryService
+import org.onap.cps.rest.controller.CpsRestInputMapper
 import org.onap.cps.spi.exceptions.AlreadyDefinedException
 import org.onap.cps.spi.exceptions.CpsException
 import org.onap.cps.spi.exceptions.CpsPathException
@@ -71,10 +73,10 @@ class CpsRestExceptionHandlerSpec extends Specification {
     CpsQueryService mockCpsQueryService = Stub()
 
     @SpringBean
-    ModelMapper modelMapper = Stub()
+    JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
 
     @SpringBean
-    JsonObjectMapper jsonObjectMapper = Stub()
+    CpsRestInputMapper cpsRestInputMapper = Stub()
 
     @Autowired
     MockMvc mvc
index 37d9315..98a392a 100644 (file)
@@ -3,7 +3,7 @@
   ============LICENSE_START=======================================================\r
   Copyright (C) 2020-2021 Pantheon.tech\r
   Modifications Copyright (C) 2020-2021 Bell Canada\r
-  Modifications Copyright (C) 2021 Nordix Foundation\r
+  Modifications Copyright (C) 2020-2022 Nordix Foundation\r
   ================================================================================\r
   Licensed under the Apache License, Version 2.0 (the "License");\r
   you may not use this file except in compliance with the License.\r
@@ -26,7 +26,7 @@
     <parent>\r
         <groupId>org.onap.cps</groupId>\r
         <artifactId>cps-parent</artifactId>\r
-        <version>3.0.0-SNAPSHOT</version>\r
+        <version>3.1.0-SNAPSHOT</version>\r
         <relativePath>../cps-parent/pom.xml</relativePath>\r
     </parent>\r
 \r
             <groupId>org.projectlombok</groupId>\r
             <artifactId>lombok</artifactId>\r
         </dependency>\r
-        <dependency>\r
-            <groupId>org.modelmapper</groupId>\r
-            <artifactId>modelmapper</artifactId>\r
-        </dependency>\r
         <dependency>\r
             <groupId>org.liquibase</groupId>\r
             <artifactId>liquibase-core</artifactId>\r
index 78862d7..bb3c2d0 100644 (file)
@@ -56,6 +56,7 @@ import org.onap.cps.spi.model.DataNodeBuilder;
 import org.onap.cps.spi.repository.AnchorRepository;
 import org.onap.cps.spi.repository.DataspaceRepository;
 import org.onap.cps.spi.repository.FragmentRepository;
+import org.onap.cps.spi.utils.SessionManager;
 import org.onap.cps.utils.JsonObjectMapper;
 import org.springframework.dao.DataIntegrityViolationException;
 import org.springframework.stereotype.Service;
@@ -73,6 +74,8 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
 
     private final JsonObjectMapper jsonObjectMapper;
 
+    private final SessionManager sessionManager;
+
     private static final String REG_EX_FOR_OPTIONAL_LIST_INDEX = "(\\[@[\\s\\S]+?]){0,1})";
     private static final Pattern REG_EX_PATTERN_FOR_LIST_ELEMENT_KEY_PREDICATE =
             Pattern.compile("\\[(\\@([^\\/]{0,9999}))\\]$");
@@ -199,6 +202,16 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
             .collect(Collectors.toUnmodifiableList());
     }
 
+    @Override
+    public String startSession() {
+        return sessionManager.startSession();
+    }
+
+    @Override
+    public void closeSession(final String sessionId) {
+        sessionManager.closeSession(sessionId);
+    }
+
     private static Set<String> processAncestorXpath(final List<FragmentEntity> fragmentEntities,
         final CpsPathQuery cpsPathQuery) {
         final Set<String> ancestorXpath = new HashSet<>();
diff --git a/cps-ri/src/main/java/org/onap/cps/spi/utils/SessionManager.java b/cps-ri/src/main/java/org/onap/cps/spi/utils/SessionManager.java
new file mode 100644 (file)
index 0000000..eb535ec
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2021-2022 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  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.utils;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import org.hibernate.HibernateException;
+import org.hibernate.Session;
+import org.hibernate.SessionException;
+import org.hibernate.SessionFactory;
+import org.hibernate.cfg.Configuration;
+import org.onap.cps.spi.entities.AnchorEntity;
+import org.onap.cps.spi.entities.DataspaceEntity;
+import org.onap.cps.spi.entities.SchemaSetEntity;
+import org.onap.cps.spi.entities.YangResourceEntity;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SessionManager {
+
+    private static SessionFactory sessionFactory;
+    private static Map<String, Session> sessionMap = new HashMap<>();
+
+    private synchronized void buildSessionFactory() {
+        if (sessionFactory == null) {
+            sessionFactory = new Configuration().configure("hibernate.cfg.xml")
+                    .addAnnotatedClass(AnchorEntity.class)
+                    .addAnnotatedClass(DataspaceEntity.class)
+                    .addAnnotatedClass(SchemaSetEntity.class)
+                    .addAnnotatedClass(YangResourceEntity.class)
+                    .buildSessionFactory();
+        }
+    }
+
+    /**
+     * Starts a session which allows use of locks and batch interaction with the persistence service.
+     *
+     * @return Session ID string
+     */
+    public String startSession() {
+        buildSessionFactory();
+        final Session session = sessionFactory.openSession();
+        final String sessionId = UUID.randomUUID().toString();
+        sessionMap.put(sessionId, session);
+        session.beginTransaction();
+        return sessionId;
+    }
+
+    /**
+     * Close session.
+     *
+     * @param sessionId session ID
+     */
+    public void closeSession(final String sessionId) {
+        try {
+            final Session currentSession = sessionMap.get(sessionId);
+            currentSession.getTransaction().commit();
+            currentSession.close();
+        } catch (final NullPointerException e) {
+            throw new SessionException(String.format("Session with session ID %s does not exist", sessionId));
+        } catch (final HibernateException e) {
+            throw new SessionException(String.format("Unable to close session with session ID %s", sessionId));
+        }
+        sessionMap.remove(sessionId);
+    }
+
+}
\ No newline at end of file
diff --git a/cps-ri/src/main/resources/hibernate.cfg.xml b/cps-ri/src/main/resources/hibernate.cfg.xml
new file mode 100644 (file)
index 0000000..98e6cfc
--- /dev/null
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE hibernate-configuration PUBLIC
+        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
+        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
+
+<hibernate-configuration>
+    <session-factory>
+        <property name="hibernate.connection.driver_class">org.postgresql.Driver</property>
+        <property name="hibernate.connection.url">jdbc:postgresql://${DB_HOST}:${DB_PORT:5432}/cpsdb</property>
+        <property name="hibernate.connection.username">${DB_USERNAME}</property>
+        <property name="hibernate.connection.password">${DB_PASSWORD}</property>
+        <property name="hibernate.dialect">org.hibernate.dialect.PostgreSQL82Dialect</property>
+        <property name="show_sql">true</property>
+        <property name="hibernate.hbm2ddl.auto">update</property>
+    </session-factory>
+</hibernate-configuration>
\ No newline at end of file
index 7166008..c508762 100644 (file)
@@ -28,19 +28,20 @@ import org.onap.cps.spi.model.DataNodeBuilder
 import org.onap.cps.spi.repository.AnchorRepository
 import org.onap.cps.spi.repository.DataspaceRepository
 import org.onap.cps.spi.repository.FragmentRepository
+import org.onap.cps.spi.utils.SessionManager
 import org.onap.cps.utils.JsonObjectMapper
 import spock.lang.Specification
 
-
 class CpsDataPersistenceServiceSpec extends Specification {
 
     def mockDataspaceRepository = Mock(DataspaceRepository)
     def mockAnchorRepository = Mock(AnchorRepository)
     def mockFragmentRepository = Mock(FragmentRepository)
     def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
+    def mockSessionManager = Mock(SessionManager)
 
     def objectUnderTest = new CpsDataPersistenceServiceImpl(
-            mockDataspaceRepository, mockAnchorRepository, mockFragmentRepository, jsonObjectMapper)
+            mockDataspaceRepository, mockAnchorRepository, mockFragmentRepository, jsonObjectMapper,mockSessionManager)
 
     def 'Handling of StaleStateException (caused by concurrent updates) during data node tree update.'() {
 
@@ -49,67 +50,82 @@ class CpsDataPersistenceServiceSpec extends Specification {
         def myAnchorName = 'my-anchor'
 
         given: 'data node object'
-            def submittedDataNode = new DataNodeBuilder()
-                    .withXpath(parentXpath)
-                    .withLeaves(['leaf-name': 'leaf-value'])
-                    .build()
+        def submittedDataNode = new DataNodeBuilder()
+                .withXpath(parentXpath)
+                .withLeaves(['leaf-name': 'leaf-value'])
+                .build()
         and: 'fragment to be updated'
-            mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> {
-                def fragmentEntity = new FragmentEntity()
-                fragmentEntity.setXpath(parentXpath)
-                fragmentEntity.setChildFragments(Collections.emptySet())
-                return fragmentEntity
-            }
+        mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> {
+            def fragmentEntity = new FragmentEntity()
+            fragmentEntity.setXpath(parentXpath)
+            fragmentEntity.setChildFragments(Collections.emptySet())
+            return fragmentEntity
+        }
         and: 'data node is concurrently updated by another transaction'
-            mockFragmentRepository.save(_) >> { throw new StaleStateException("concurrent updates") }
+        mockFragmentRepository.save(_) >> { throw new StaleStateException("concurrent updates") }
 
         when: 'attempt to update data node'
-            objectUnderTest.replaceDataNodeTree(myDataspaceName, myAnchorName, submittedDataNode)
+        objectUnderTest.replaceDataNodeTree(myDataspaceName, myAnchorName, submittedDataNode)
 
         then: 'concurrency exception is thrown'
-            def concurrencyException = thrown(ConcurrencyException)
-            assert concurrencyException.getDetails().contains(myDataspaceName)
-            assert concurrencyException.getDetails().contains(myAnchorName)
-            assert concurrencyException.getDetails().contains(parentXpath)
+        def concurrencyException = thrown(ConcurrencyException)
+        assert concurrencyException.getDetails().contains(myDataspaceName)
+        assert concurrencyException.getDetails().contains(myAnchorName)
+        assert concurrencyException.getDetails().contains(parentXpath)
     }
 
     def 'Retrieving a data node with a property JSON value of #scenario'() {
         given: 'a fragment with a property JSON value of #scenario'
-            mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> {
-                new FragmentEntity(childFragments: Collections.emptySet(),
-                        attributes: "{\"some attribute\": ${dataString}}")
-            }
+        mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> {
+            new FragmentEntity(childFragments: Collections.emptySet(),
+                    attributes: "{\"some attribute\": ${dataString}}")
+        }
         when: 'getting the data node represented by this fragment'
-            def dataNode = objectUnderTest.getDataNode('my-dataspace', 'my-anchor',
-                    'parent-01', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
+        def dataNode = objectUnderTest.getDataNode('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')
-            assert attributeValue == expectedValue
-            assert attributeValue.class == expectedDataClass
+        def attributeValue = dataNode.leaves.get('some attribute')
+        assert attributeValue == expectedValue
+        assert attributeValue.class == expectedDataClass
         where: 'the following Data Type is passed'
-            scenario                              | dataString            || expectedValue     | expectedDataClass
-            'just numbers'                        | '15174'               || 15174             | Integer
-            'number with dot'                     | '15174.32'            || 15174.32          | Double
-            'number with 0 value after dot'       | '15174.0'             || 15174.0           | Double
-            'number with 0 value before dot'      | '0.32'                || 0.32              | Double
-            'number higher than max int'          | '2147483648'          || 2147483648        | Long
-            'just text'                           | '"Test"'              || 'Test'            | String
-            'number with exponent'                | '1.2345e5'            || 1.2345e5          | Double
-            'number higher than max int with dot' | '123456789101112.0'   || 123456789101112.0 | Double
-            'text and numbers'                    | '"String = \'1234\'"' || "String = '1234'" | String
-            'number as String'                    | '"12345"'             || '12345'           | String
+        scenario                              | dataString            || expectedValue     | expectedDataClass
+        'just numbers'                        | '15174'               || 15174             | Integer
+        'number with dot'                     | '15174.32'            || 15174.32          | Double
+        'number with 0 value after dot'       | '15174.0'             || 15174.0           | Double
+        'number with 0 value before dot'      | '0.32'                || 0.32              | Double
+        'number higher than max int'          | '2147483648'          || 2147483648        | Long
+        'just text'                           | '"Test"'              || 'Test'            | String
+        'number with exponent'                | '1.2345e5'            || 1.2345e5          | Double
+        'number higher than max int with dot' | '123456789101112.0'   || 123456789101112.0 | Double
+        'text and numbers'                    | '"String = \'1234\'"' || "String = '1234'" | String
+        'number as String'                    | '"12345"'             || '12345'           | String
     }
 
     def 'Retrieving a data node with invalid JSON'() {
         given: 'a fragment with invalid JSON'
-            mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> {
-                new FragmentEntity(childFragments: Collections.emptySet(), attributes: '{invalid json')
-            }
+        mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, _) >> {
+            new FragmentEntity(childFragments: Collections.emptySet(), attributes: '{invalid json')
+        }
         when: 'getting the data node represented by this fragment'
-            def dataNode = objectUnderTest.getDataNode('my-dataspace', 'my-anchor',
+        def dataNode = objectUnderTest.getDataNode('my-dataspace', 'my-anchor',
                 'parent-01', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS)
         then: 'a data validation exception is thrown'
-            thrown(DataValidationException)
+        thrown(DataValidationException)
     }
 
-}
+    def 'start session'() {
+        when: 'start session'
+            objectUnderTest.startSession()
+        then: 'the session manager method to start session is invoked'
+            1 * mockSessionManager.startSession()
+    }
+
+    def 'close session'() {
+        given: 'session ID'
+            def someSessionId = 'someSessionId'
+        when: 'close session method is called with session ID as parameter'
+            objectUnderTest.closeSession(someSessionId)
+        then: 'the session manager method to close session is invoked with parameter'
+            1 * mockSessionManager.closeSession(someSessionId)
+    }
+}
\ No newline at end of file
index 085bb33..214fd69 100644 (file)
@@ -21,29 +21,20 @@ package org.onap.cps.spi.impl
 
 import com.fasterxml.jackson.databind.ObjectMapper
 import org.hibernate.exception.ConstraintViolationException
-import org.mockito.InjectMocks
-import org.mockito.Mock
 import org.onap.cps.spi.CpsAdminPersistenceService
 import org.onap.cps.spi.CpsModulePersistenceService
 import org.onap.cps.spi.entities.DataspaceEntity
-import org.onap.cps.spi.entities.YangResourceEntity
 import org.onap.cps.spi.exceptions.DuplicatedYangResourceException
-import org.onap.cps.spi.model.ExtendedModuleReference
 import org.onap.cps.spi.model.ModuleReference
 import org.onap.cps.spi.repository.AnchorRepository
 import org.onap.cps.spi.repository.DataspaceRepository
-import org.onap.cps.spi.repository.FragmentRepository
 import org.onap.cps.spi.repository.SchemaSetRepository
 import org.onap.cps.spi.repository.YangResourceRepository
 import org.onap.cps.utils.JsonObjectMapper
 import org.spockframework.spring.SpringBean
 import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.boot.test.context.SpringBootTest
-import org.springframework.boot.test.mock.mockito.MockBean
 import org.springframework.dao.DataIntegrityViolationException
-import org.springframework.test.context.jdbc.Sql
 import spock.lang.Shared
-import spock.lang.Specification
 
 import java.sql.SQLException
 
index 1b37bef..c4cfa3d 100644 (file)
@@ -27,9 +27,7 @@ import org.onap.cps.spi.exceptions.AlreadyDefinedException
 import org.onap.cps.spi.exceptions.DataspaceNotFoundException
 import org.onap.cps.spi.exceptions.SchemaSetNotFoundException
 import org.onap.cps.spi.model.ModuleReference
-import org.onap.cps.spi.model.ExtendedModuleReference
 import org.onap.cps.spi.repository.AnchorRepository
-import org.onap.cps.spi.repository.ModuleReferenceRepository
 import org.onap.cps.spi.repository.SchemaSetRepository
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.test.context.jdbc.Sql
@@ -68,7 +66,7 @@ class CpsModulePersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase
     static final String NEW_RESOURCE_CHECKSUM = 'b13faef573ed1374139d02c40d8ce09c80ea1dc70e63e464c1ed61568d48d539'
     static final String NEW_RESOURCE_MODULE_NAME = 'stores'
     static final String NEW_RESOURCE_REVISION = '2020-09-15'
-    static final ExtendedModuleReference newModuleReference = ExtendedModuleReference.builder().name(NEW_RESOURCE_MODULE_NAME)
+    static final ModuleReference newModuleReference = ModuleReference.builder().moduleName(NEW_RESOURCE_MODULE_NAME)
             .revision(NEW_RESOURCE_REVISION).build()
 
     def newYangResourcesNameToContentMap = [(NEW_RESOURCE_NAME):NEW_RESOURCE_CONTENT]
diff --git a/cps-ri/src/test/groovy/org/onap/cps/spi/utils/SessionManagerIntegrationSpec.groovy b/cps-ri/src/test/groovy/org/onap/cps/spi/utils/SessionManagerIntegrationSpec.groovy
new file mode 100644 (file)
index 0000000..c46092f
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2021-2022 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  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.utils
+
+import org.hibernate.SessionException
+import org.onap.cps.spi.impl.CpsPersistenceSpecBase
+
+class SessionManagerIntegrationSpec extends CpsPersistenceSpecBase{
+
+    def objectUnderTest = new SessionManager();
+
+    def 'start session'() {
+        when: 'start session'
+            def result = objectUnderTest.startSession()
+        then: 'session ID is returned'
+            assert result instanceof String
+            objectUnderTest.closeSession(result)
+    }
+
+    def 'close session'(){
+        given: 'session Id from calling the start session method'
+            def sessionId = objectUnderTest.startSession()
+        when: 'close session method is called'
+            objectUnderTest.closeSession(sessionId)
+        then: 'no exception is thrown'
+            noExceptionThrown()
+    }
+
+    def 'close session that does not exist' (){
+        given: 'session Id that does not exist'
+            def unknownSessionId = 'unknown session id'
+        when: 'close session method is called'
+            objectUnderTest.closeSession(unknownSessionId)
+        then: 'a session exception is thrown'
+            def thrown = thrown(SessionException)
+            assert thrown.message.contains(unknownSessionId)
+    }
+}
diff --git a/cps-ri/src/test/resources/hibernate.cfg.xml b/cps-ri/src/test/resources/hibernate.cfg.xml
new file mode 100644 (file)
index 0000000..fae9275
--- /dev/null
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<!DOCTYPE hibernate-configuration PUBLIC\r
+        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"\r
+        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">\r
+\r
+<hibernate-configuration>\r
+    <session-factory>\r
+        <property name="hibernate.connection.driver_class">org.postgresql.Driver</property>\r
+        <property name="hibernate.connection.url">${DB_URL}</property>\r
+        <property name="hibernate.connection.username">${DB_USERNAME}</property>\r
+        <property name="hibernate.connection.password">${DB_PASSWORD}</property>\r
+        <property name="hibernate.dialect">org.hibernate.dialect.PostgreSQL82Dialect</property>\r
+        <property name="show_sql">true</property>\r
+        <property name="hibernate.hbm2ddl.auto">none</property>\r
+    </session-factory>\r
+</hibernate-configuration>
\ No newline at end of file
index 9c7031e..aea122d 100644 (file)
@@ -28,7 +28,7 @@
   <parent>\r
     <groupId>org.onap.cps</groupId>\r
     <artifactId>cps-parent</artifactId>\r
-    <version>3.0.0-SNAPSHOT</version>\r
+    <version>3.1.0-SNAPSHOT</version>\r
     <relativePath>../cps-parent/pom.xml</relativePath>\r
   </parent>\r
 \r
index cdd417b..35caf95 100644 (file)
@@ -174,4 +174,19 @@ public interface CpsDataService {
      */
     void updateNodeLeavesAndExistingDescendantLeaves(String dataspaceName, String anchorName, String parentNodeXpath,
         String dataNodeUpdatesAsJson, OffsetDateTime observedTimestamp);
+
+    /**
+     * Starts a session which allows use of locks and batch interaction with the persistence service.
+     *
+     * @return Session ID string
+     */
+    String startSession();
+
+    /**
+     * Close session.
+     *
+     * @param sessionId session ID
+     *
+     */
+    void closeSession(String sessionId);
 }
index 6ae28fe..ecc9bf0 100644 (file)
@@ -96,9 +96,10 @@ public interface CpsModuleService {
 
     /**
      * Identify previously unknown Yang Resource module references.
+     * The system will ignore the namespace of all module references.
      *
      * @param moduleReferencesToCheck the moduleReferencesToCheck
-     * @returns collection of module references
+     * @returns collection of module references (namespace will be always blank)
      */
     Collection<ModuleReference> identifyNewModuleReferences(
         Collection<ModuleReference> moduleReferencesToCheck);
index aae355d..643614f 100755 (executable)
@@ -108,6 +108,17 @@ public class CpsDataServiceImpl implements CpsDataService {
         processDataUpdatedEventAsync(dataspaceName, anchorName, observedTimestamp, parentNodeXpath, Operation.UPDATE);
     }
 
+    @Override
+    public String startSession() {
+        final String sessionId = cpsDataPersistenceService.startSession();
+        return sessionId;
+    }
+
+    @Override
+    public void closeSession(final String sessionId) {
+        cpsDataPersistenceService.closeSession(sessionId);
+    }
+
     @Override
     public void replaceNodeTree(final String dataspaceName, final String anchorName, final String parentNodeXpath,
         final String jsonData, final OffsetDateTime observedTimestamp) {
index 7267f22..f0e79c6 100644 (file)
@@ -68,7 +68,7 @@ public class CpsModuleServiceImpl implements CpsModuleService {
         final var yangTextSchemaSourceSet = yangTextSchemaSourceSetCache
             .get(dataspaceName, schemaSetName);
         return SchemaSet.builder().name(schemaSetName).dataspaceName(dataspaceName)
-            .extendedModuleReferences(yangTextSchemaSourceSet.getModuleReferences()).build();
+            .moduleReferences(yangTextSchemaSourceSet.getModuleReferences()).build();
     }
 
     @Override
index 5e26a22..30bb851 100644 (file)
@@ -1,6 +1,7 @@
 /*
  * ============LICENSE_START=======================================================
  * Copyright (c) 2021-2022 Bell Canada.
+ * Modifications 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.
@@ -28,6 +29,8 @@ import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Future;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
+import javax.annotation.PostConstruct;
+import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.spi.model.Anchor;
 import org.springframework.scheduling.annotation.Async;
@@ -35,32 +38,18 @@ import org.springframework.stereotype.Service;
 
 @Service
 @Slf4j
+@RequiredArgsConstructor
 public class NotificationService {
 
-    private NotificationProperties notificationProperties;
-    private NotificationPublisher notificationPublisher;
-    private CpsDataUpdatedEventFactory cpsDataUpdatedEventFactory;
-    private NotificationErrorHandler notificationErrorHandler;
+    private final NotificationProperties notificationProperties;
+    private final NotificationPublisher notificationPublisher;
+    private final CpsDataUpdatedEventFactory cpsDataUpdatedEventFactory;
+    private final NotificationErrorHandler notificationErrorHandler;
     private List<Pattern> dataspacePatterns;
 
-    /**
-     * Create an instance of Notification Subscriber.
-     *
-     * @param notificationProperties     properties for notification
-     * @param notificationPublisher      notification Publisher
-     * @param cpsDataUpdatedEventFactory to create CPSDataUpdatedEvent
-     * @param notificationErrorHandler   error handler
-     */
-    public NotificationService(
-        final NotificationProperties notificationProperties,
-        final NotificationPublisher notificationPublisher,
-        final CpsDataUpdatedEventFactory cpsDataUpdatedEventFactory,
-        final NotificationErrorHandler notificationErrorHandler) {
+    @PostConstruct
+    public void init() {
         log.info("Notification Properties {}", notificationProperties);
-        this.notificationProperties = notificationProperties;
-        this.notificationPublisher = notificationPublisher;
-        this.cpsDataUpdatedEventFactory = cpsDataUpdatedEventFactory;
-        this.notificationErrorHandler = notificationErrorHandler;
         this.dataspacePatterns = getDataspaceFilterPatterns(notificationProperties);
     }
 
index fd65886..fdcf15b 100644 (file)
@@ -148,4 +148,17 @@ public interface CpsDataPersistenceService {
     Collection<DataNode> queryDataNodes(String dataspaceName, String anchorName,
         String cpsPath, FetchDescendantsOption fetchDescendantsOption);
 
+    /**
+     * Starts a session which allows use of locks and batch interaction with the persistence service.
+     *
+     * @return Session ID string
+     */
+    String startSession();
+
+    /**
+     * Close session.
+     *
+     * @param sessionId session ID
+     */
+    void closeSession(String sessionId);
 }
index 4306df7..0e90e84 100755 (executable)
@@ -100,9 +100,11 @@ public interface CpsModulePersistenceService {
 
     /**
      * Identify new module references from those returned by a node compared to what is in CPS already.
+     * The system will ignore the namespace of all module references.
      *
      * @param moduleReferencesToCheck the module references ot check
-     * @returns Collection of {@link ModuleReference} of previously unknown module references
+     * @returns Collection of {@link ModuleReference} (namespace will be always blank)
+     *
      */
     Collection<ModuleReference> identifyNewModuleReferences(
         Collection<ModuleReference> moduleReferencesToCheck);
index 8e9dff8..55e7b99 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- * Copyright (C) 2020-2021 Nordix Foundation.
+ * Copyright (C) 2020-2022 Nordix Foundation.
  * Modifications Copyright (C) 2021 Bell Canada.
  * Modifications Copyright (C) 2021 Pantheon.tech
  * ================================================================================
@@ -38,7 +38,7 @@ public class DataNode {
     private String dataspace;
     private String schemaSetName;
     private String anchorName;
-    private ExtendedModuleReference extendedModuleReference;
+    private ModuleReference moduleReference;
     private String xpath;
     private Map<String, Object> leaves = Collections.emptyMap();
     private Collection<String> xpathsChildren;
diff --git a/cps-service/src/main/java/org/onap/cps/spi/model/ExtendedModuleReference.java b/cps-service/src/main/java/org/onap/cps/spi/model/ExtendedModuleReference.java
deleted file mode 100644 (file)
index 5e9c8d0..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- * Copyright (C) 2020 Nordix Foundation.
- * Modifications Copyright 2020-2021 Pantheon.tech
- * ================================================================================
- * 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.model;
-
-import java.io.Serializable;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-@Data
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-public class ExtendedModuleReference implements Serializable {
-
-    private static final long serialVersionUID = 1L;
-
-    private String name;
-    private String namespace;
-    private String revision;
-
-}
index 9b73f8f..569f0a0 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- * Copyright (C) 2021 Nordix Foundation.
+ * Copyright (C) 2021-2022 Nordix Foundation.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -35,4 +35,18 @@ public class ModuleReference implements Serializable {
     private static final long serialVersionUID = -1761408847591042599L;
     private String moduleName;
     private String revision;
+    @Builder.Default
+    private String namespace = "";
+
+    /**
+     * Constructor for module references without namespace (will remain blank).
+     *
+     * @param moduleName module names.
+     * @param revision   revision of module.
+     */
+    public ModuleReference(final String moduleName, final String revision) {
+        this.moduleName = moduleName;
+        this.revision = revision;
+        this.namespace = "";
+    }
 }
index 4df7893..bb98148 100644 (file)
@@ -1,12 +1,14 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2020 Pantheon.tech
+ *  Modifications 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.
@@ -35,5 +37,5 @@ public class SchemaSet implements Serializable {
     private static final long serialVersionUID = 1464791260718603291L;
     private String name;
     private String dataspaceName;
-    private List<ExtendedModuleReference> extendedModuleReferences;
+    private List<ModuleReference> moduleReferences;
 }
index 2c9d374..80f0224 100644 (file)
@@ -1,12 +1,14 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2020 Pantheon.tech
+ *  Modifications 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.
@@ -20,8 +22,7 @@
 package org.onap.cps.yang;
 
 import java.util.List;
-import org.checkerframework.checker.nullness.qual.NonNull;
-import org.onap.cps.spi.model.ExtendedModuleReference;
+import org.onap.cps.spi.model.ModuleReference;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 
 /**
@@ -34,13 +35,11 @@ public interface YangTextSchemaSourceSet {
      *
      * @return list of ModuleRef
      */
-    @NonNull
-    List<ExtendedModuleReference> getModuleReferences();
+    List<ModuleReference> getModuleReferences();
 
     /**
      *  Return SchemaContext for given YangSchema.
      * @return SchemaContext
      */
-    @NonNull
     SchemaContext getSchemaContext();
 }
index 5cbfd62..fd53497 100644 (file)
@@ -1,12 +1,14 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2020 Pantheon.tech
+ *  Modifications 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.
@@ -35,7 +37,7 @@ import java.util.stream.Collectors;
 import lombok.NoArgsConstructor;
 import org.onap.cps.spi.exceptions.CpsException;
 import org.onap.cps.spi.exceptions.ModelValidationException;
-import org.onap.cps.spi.model.ExtendedModuleReference;
+import org.onap.cps.spi.model.ModuleReference;
 import org.opendaylight.yangtools.yang.common.Revision;
 import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
@@ -88,15 +90,15 @@ public final class YangTextSchemaSourceSetBuilder {
         }
 
         @Override
-        public List<ExtendedModuleReference> getModuleReferences() {
+        public List<ModuleReference> getModuleReferences() {
             return schemaContext.getModules().stream()
                 .map(YangTextSchemaSourceSetImpl::toModuleReference)
                 .collect(Collectors.toList());
         }
 
-        private static ExtendedModuleReference toModuleReference(final Module module) {
-            return ExtendedModuleReference.builder()
-                .name(module.getName())
+        private static ModuleReference toModuleReference(final Module module) {
+            return ModuleReference.builder()
+                .moduleName(module.getName())
                 .namespace(module.getQNameModule().getNamespace().toString())
                 .revision(module.getRevision().map(Revision::toString).orElse(null))
                 .build();
index 785788b..eb06199 100644 (file)
@@ -254,4 +254,20 @@ class CpsDataServiceImplSpec extends Specification {
         def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent).getSchemaContext()
         mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext
     }
+
+    def 'start session'() {
+        when: 'start session method is called'
+            objectUnderTest.startSession()
+        then: 'the persistence service method to start session is invoked'
+            1 * mockCpsDataPersistenceService.startSession()
+    }
+
+    def 'close session'(){
+        given: 'session Id from calling the start session method'
+            def sessionId = objectUnderTest.startSession()
+        when: 'close session method is called'
+            objectUnderTest.closeSession(sessionId)
+        then: 'the persistence service method to close session is invoked'
+            1 * mockCpsDataPersistenceService.closeSession(sessionId)
+    }
 }
index afd8e86..bae06bb 100644 (file)
@@ -28,7 +28,6 @@ import org.onap.cps.spi.CpsModulePersistenceService
 import org.onap.cps.spi.exceptions.ModelValidationException
 import org.onap.cps.spi.exceptions.SchemaSetInUseException
 import org.onap.cps.spi.model.Anchor
-import org.onap.cps.spi.model.ExtendedModuleReference
 import org.onap.cps.spi.model.ModuleReference
 import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
 import spock.lang.Specification
@@ -54,7 +53,7 @@ class CpsModuleServiceImplSpec extends Specification {
 
     def 'Create schema set from new modules and existing modules.'() {
         given: 'a list of existing modules module reference'
-            def moduleReferenceForExistingModule = new ExtendedModuleReference("test", "test.org", "2021-10-12")
+            def moduleReferenceForExistingModule = new ModuleReference("test",  "2021-10-12","test.org")
             def listOfExistingModulesModuleReference = [moduleReferenceForExistingModule]
         when: 'create schema set from modules method is invoked'
             objectUnderTest.createSchemaSetFromModules("someDataspaceName", "someSchemaSetName", [newModule: "newContent"], listOfExistingModulesModuleReference)
@@ -81,7 +80,7 @@ class CpsModuleServiceImplSpec extends Specification {
         then: 'the correct schema set is returned'
             result.getName().contains('someSchemaSet')
             result.getDataspaceName().contains('someDataspace')
-            result.getExtendedModuleReferences().contains(new ExtendedModuleReference('stores', 'org:onap:ccsdk:sample', '2020-09-15'))
+            result.getModuleReferences().contains(new ModuleReference('stores', '2020-09-15', 'org:onap:ccsdk:sample'))
     }
 
     def 'Delete schema-set when cascade is allowed.'() {
@@ -134,7 +133,7 @@ class CpsModuleServiceImplSpec extends Specification {
 
     def 'Get all yang resources module references.'() {
         given: 'an already present module reference'
-            def moduleReferences = [new ExtendedModuleReference()]
+            def moduleReferences = [new ModuleReference('some module name','some revision name')]
             mockCpsModulePersistenceService.getYangResourceModuleReferences('someDataspaceName') >> moduleReferences
         expect: 'the list provided by persistence service is returned as result'
             objectUnderTest.getYangResourceModuleReferences('someDataspaceName') == moduleReferences
index 203151b..135040f 100644 (file)
@@ -49,8 +49,6 @@ CPS Log pattern
 Change logging level
 --------------------
 
-.. container:: ulist
-
 - Curl command 1. Check current log level of "logging.level.org.onap.cps" if it is set to it's default value (INFO)
 
 .. code-block:: java
index 2fc8d7f..983252f 100644 (file)
@@ -15,27 +15,28 @@ info:
   x-logo:
     url: cps_logo.png
 servers:
-  - url: /cps/api
+- url: /cps/api
 tags:
-  - name: cps-admin
-    description: cps Admin
-  - name: cps-data
-    description: cps Data
+- name: cps-admin
+  description: cps Admin
+- name: cps-data
+  description: cps Data
 paths:
   /v1/dataspaces:
     post:
       tags:
-        - cps-admin
+      - cps-admin
       summary: Create a dataspace
       description: Create a new dataspace
       operationId: createDataspace
       parameters:
-        - name: dataspace-name
-          in: query
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
+      - name: dataspace-name
+        in: query
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
       responses:
         "201":
           description: Created
@@ -43,38 +44,130 @@ paths:
             text/plain:
               schema:
                 type: string
+                example: my-resource
+        "401":
+          description: Unauthorized
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 401
+                message: Unauthorized request
+                details: This request is unauthorized
+        "403":
+          description: Forbidden
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 403
+                message: Request Forbidden
+                details: This request is forbidden
+        "409":
+          description: Conflict
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 409
+                message: Conflicting request
+                details: The request cannot be processed as the resource is in use.
+        "500":
+          description: Internal Server Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 500
+                message: Internal Server Error
+                details: Internal Server Error occurred
+    delete:
+      tags:
+      - cps-admin
+      summary: Delete a dataspace
+      description: Delete a dataspace
+      operationId: deleteDataspace
+      parameters:
+      - name: dataspace-name
+        in: query
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      responses:
+        "204":
+          description: No Content
+          content: {}
         "400":
           description: Bad Request
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 400
+                message: Bad Request
+                details: The provided request is not valid
         "401":
           description: Unauthorized
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 401
+                message: Unauthorized request
+                details: This request is unauthorized
         "403":
           description: Forbidden
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 403
+                message: Request Forbidden
+                details: This request is forbidden
+        "409":
+          description: Conflict
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 409
+                message: Conflicting request
+                details: The request cannot be processed as the resource is in use.
+        "500":
+          description: Internal Server Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 500
+                message: Internal Server Error
+                details: Internal Server Error occurred
   /v1/dataspaces/{dataspace-name}/anchors:
     get:
       tags:
-        - cps-admin
+      - cps-admin
       summary: Get anchors
       description: "Read all anchors, given a dataspace"
       operationId: getAnchors
       parameters:
-        - name: dataspace-name
-          in: path
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
       responses:
         "200":
           description: OK
@@ -90,49 +183,68 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 400
+                message: Bad Request
+                details: The provided request is not valid
         "401":
           description: Unauthorized
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 401
+                message: Unauthorized request
+                details: This request is unauthorized
         "403":
           description: Forbidden
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
-        "404":
-          description: The specified resource was not found
+              example:
+                status: 403
+                message: Request Forbidden
+                details: This request is forbidden
+        "500":
+          description: Internal Server Error
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 500
+                message: Internal Server Error
+                details: Internal Server Error occurred
     post:
       tags:
-        - cps-admin
+      - cps-admin
       summary: Create an anchor
       description: Create a new anchor in the given dataspace
       operationId: createAnchor
       parameters:
-        - name: dataspace-name
-          in: path
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-        - name: schema-set-name
-          in: query
-          description: schema-set-name
-          required: true
-          schema:
-            type: string
-        - name: anchor-name
-          in: query
-          description: anchor-name
-          required: true
-          schema:
-            type: string
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: schema-set-name
+        in: query
+        description: schema-set-name
+        required: true
+        schema:
+          type: string
+          example: my-schema-set
+      - name: anchor-name
+        in: query
+        description: anchor-name
+        required: true
+        schema:
+          type: string
+          example: my-anchor
       responses:
         "201":
           description: Created
@@ -140,44 +252,79 @@ paths:
             text/plain:
               schema:
                 type: string
+                example: my-resource
         "400":
           description: Bad Request
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 400
+                message: Bad Request
+                details: The provided request is not valid
         "401":
           description: Unauthorized
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 401
+                message: Unauthorized request
+                details: This request is unauthorized
         "403":
           description: Forbidden
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 403
+                message: Request Forbidden
+                details: This request is forbidden
+        "409":
+          description: Conflict
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 409
+                message: Conflicting request
+                details: The request cannot be processed as the resource is in use.
+        "500":
+          description: Internal Server Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 500
+                message: Internal Server Error
+                details: Internal Server Error occurred
   /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}:
     get:
       tags:
-        - cps-admin
+      - cps-admin
       summary: Get an anchor
       description: Read an anchor given an anchor name and a dataspace
       operationId: getAnchor
       parameters:
-        - name: dataspace-name
-          in: path
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-        - name: anchor-name
-          in: path
-          description: anchor-name
-          required: true
-          schema:
-            type: string
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: anchor-name
+        in: path
+        description: anchor-name
+        required: true
+        schema:
+          type: string
+          example: my-anchor
       responses:
         "200":
           description: OK
@@ -191,43 +338,61 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 400
+                message: Bad Request
+                details: The provided request is not valid
         "401":
           description: Unauthorized
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 401
+                message: Unauthorized request
+                details: This request is unauthorized
         "403":
           description: Forbidden
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
-        "404":
-          description: The specified resource was not found
+              example:
+                status: 403
+                message: Request Forbidden
+                details: This request is forbidden
+        "500":
+          description: Internal Server Error
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 500
+                message: Internal Server Error
+                details: Internal Server Error occurred
     delete:
       tags:
-        - cps-admin
+      - cps-admin
       summary: Delete an anchor
       description: Delete an anchor given an anchor name and a dataspace
       operationId: deleteAnchor
       parameters:
-        - name: dataspace-name
-          in: path
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-        - name: anchor-name
-          in: path
-          description: anchor-name
-          required: true
-          schema:
-            type: string
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: anchor-name
+        in: path
+        description: anchor-name
+        required: true
+        schema:
+          type: string
+          example: my-anchor
       responses:
         "204":
           description: No Content
@@ -238,38 +403,62 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 400
+                message: Bad Request
+                details: The provided request is not valid
         "401":
           description: Unauthorized
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 401
+                message: Unauthorized request
+                details: This request is unauthorized
         "403":
           description: Forbidden
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 403
+                message: Request Forbidden
+                details: This request is forbidden
+        "500":
+          description: Internal Server Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 500
+                message: Internal Server Error
+                details: Internal Server Error occurred
   /v1/dataspaces/{dataspace-name}/schema-sets:
     post:
       tags:
-        - cps-admin
+      - cps-admin
       summary: Create a schema set
       description: Create a new schema set in the given dataspace
       operationId: createSchemaSet
       parameters:
-        - name: dataspace-name
-          in: path
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-        - name: schema-set-name
-          in: query
-          description: schema-set-name
-          required: true
-          schema:
-            type: string
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: schema-set-name
+        in: query
+        description: schema-set-name
+        required: true
+        schema:
+          type: string
+          example: my-schema-set
       requestBody:
         content:
           multipart/form-data:
@@ -283,44 +472,79 @@ paths:
             text/plain:
               schema:
                 type: string
+                example: my-resource
         "400":
           description: Bad Request
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 400
+                message: Bad Request
+                details: The provided request is not valid
         "401":
           description: Unauthorized
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 401
+                message: Unauthorized request
+                details: This request is unauthorized
         "403":
           description: Forbidden
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 403
+                message: Request Forbidden
+                details: This request is forbidden
+        "409":
+          description: Conflict
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 409
+                message: Conflicting request
+                details: The request cannot be processed as the resource is in use.
+        "500":
+          description: Internal Server Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 500
+                message: Internal Server Error
+                details: Internal Server Error occurred
   /v1/dataspaces/{dataspace-name}/schema-sets/{schema-set-name}:
     get:
       tags:
-        - cps-admin
+      - cps-admin
       summary: Get a schema set
       description: Read a schema set given a schema set name and a dataspace
       operationId: getSchemaSet
       parameters:
-        - name: dataspace-name
-          in: path
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-        - name: schema-set-name
-          in: path
-          description: schema-set-name
-          required: true
-          schema:
-            type: string
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: schema-set-name
+        in: path
+        description: schema-set-name
+        required: true
+        schema:
+          type: string
+          example: my-schema-set
       responses:
         "200":
           description: OK
@@ -334,43 +558,61 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 400
+                message: Bad Request
+                details: The provided request is not valid
         "401":
           description: Unauthorized
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 401
+                message: Unauthorized request
+                details: This request is unauthorized
         "403":
           description: Forbidden
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
-        "404":
-          description: The specified resource was not found
+              example:
+                status: 403
+                message: Request Forbidden
+                details: This request is forbidden
+        "500":
+          description: Internal Server Error
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 500
+                message: Internal Server Error
+                details: Internal Server Error occurred
     delete:
       tags:
-        - cps-admin
+      - cps-admin
       summary: Delete a schema set
       description: Delete a schema set given a schema set name and a dataspace
       operationId: deleteSchemaSet
       parameters:
-        - name: dataspace-name
-          in: path
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-        - name: schema-set-name
-          in: path
-          description: schema-set-name
-          required: true
-          schema:
-            type: string
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: schema-set-name
+        in: path
+        description: schema-set-name
+        required: true
+        schema:
+          type: string
+          example: my-schema-set
       responses:
         "204":
           description: No Content
@@ -381,59 +623,93 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 400
+                message: Bad Request
+                details: The provided request is not valid
         "401":
           description: Unauthorized
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 401
+                message: Unauthorized request
+                details: This request is unauthorized
         "403":
           description: Forbidden
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 403
+                message: Request Forbidden
+                details: This request is forbidden
         "409":
           description: Conflict
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 409
+                message: Conflicting request
+                details: The request cannot be processed as the resource is in use.
+        "500":
+          description: Internal Server Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 500
+                message: Internal Server Error
+                details: Internal Server Error occurred
   /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/node:
     get:
       tags:
-        - cps-data
+      - cps-data
       summary: Get a node
       description: Get a node with an option to retrieve all the children for a given
         anchor and dataspace
       operationId: getNodeByDataspaceAndAnchor
       parameters:
-        - name: dataspace-name
-          in: path
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-        - name: anchor-name
-          in: path
-          description: anchor-name
-          required: true
-          schema:
-            type: string
-        - name: xpath
-          in: query
-          description: xpath
-          required: false
-          schema:
-            type: string
-            default: /
-        - name: include-descendants
-          in: query
-          description: include-descendants
-          required: false
-          schema:
-            type: boolean
-            default: false
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: anchor-name
+        in: path
+        description: anchor-name
+        required: true
+        schema:
+          type: string
+          example: my-anchor
+      - name: xpath
+        in: query
+        description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html"
+        required: false
+        schema:
+          type: string
+          default: /
+        examples:
+          container xpath:
+            value: /shops/bookstore
+          list attributes xpath:
+            value: "/shops/bookstore/categories[@code=1]"
+      - name: include-descendants
+        in: query
+        description: include-descendants
+        required: false
+        schema:
+          type: boolean
+          example: false
+          default: false
       responses:
         "200":
           description: OK
@@ -441,75 +717,100 @@ paths:
             application/json:
               schema:
                 type: object
-              example:
-                child: my_child
-                leafList: "leafListElement1, leafListElement2"
-                leaf: my_leaf
+              examples:
+                dataSample:
+                  $ref: '#/components/examples/dataSample'
         "400":
           description: Bad Request
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 400
+                message: Bad Request
+                details: The provided request is not valid
         "401":
           description: Unauthorized
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 401
+                message: Unauthorized request
+                details: This request is unauthorized
         "403":
           description: Forbidden
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
-        "404":
-          description: The specified resource was not found
+              example:
+                status: 403
+                message: Request Forbidden
+                details: This request is forbidden
+        "500":
+          description: Internal Server Error
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 500
+                message: Internal Server Error
+                details: Internal Server Error occurred
       x-codegen-request-body-name: xpath
   /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes:
     put:
       tags:
-        - cps-data
+      - cps-data
       summary: Replace a node with descendants
       description: "Replace a node with descendants for a given dataspace, anchor\
         \ and a parent node xpath"
       operationId: replaceNode
       parameters:
-        - name: dataspace-name
-          in: path
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-        - name: anchor-name
-          in: path
-          description: anchor-name
-          required: true
-          schema:
-            type: string
-        - name: xpath
-          in: query
-          description: xpath
-          required: false
-          schema:
-            type: string
-            default: /
-        - name: observed-timestamp
-          in: query
-          description: observed-timestamp
-          required: false
-          schema:
-            type: string
-            example: 2021-03-21T00:10:34.030-0100
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: anchor-name
+        in: path
+        description: anchor-name
+        required: true
+        schema:
+          type: string
+          example: my-anchor
+      - name: xpath
+        in: query
+        description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html"
+        required: false
+        schema:
+          type: string
+          default: /
+        examples:
+          container xpath:
+            value: /shops/bookstore
+          list attributes xpath:
+            value: "/shops/bookstore/categories[@code=1]"
+      - name: observed-timestamp
+        in: query
+        description: observed-timestamp
+        required: false
+        schema:
+          type: string
+          example: 2021-03-21T00:10:34.030-0100
       requestBody:
         content:
           application/json:
             schema:
-              type: string
+              type: object
+            examples:
+              dataSample:
+                $ref: '#/components/examples/dataSample'
         required: true
       responses:
         "200":
@@ -518,64 +819,97 @@ paths:
             application/json:
               schema:
                 type: object
-              example:
-                key: value
+              examples:
+                dataSample:
+                  value: ""
         "400":
           description: Bad Request
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 400
+                message: Bad Request
+                details: The provided request is not valid
         "401":
           description: Unauthorized
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 401
+                message: Unauthorized request
+                details: This request is unauthorized
         "403":
           description: Forbidden
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 403
+                message: Request Forbidden
+                details: This request is forbidden
+        "500":
+          description: Internal Server Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 500
+                message: Internal Server Error
+                details: Internal Server Error occurred
     post:
       tags:
-        - cps-data
+      - cps-data
       summary: Create a node
       description: Create a node for a given anchor and dataspace
       operationId: createNode
       parameters:
-        - name: dataspace-name
-          in: path
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-        - name: anchor-name
-          in: path
-          description: anchor-name
-          required: true
-          schema:
-            type: string
-        - name: xpath
-          in: query
-          description: xpath
-          required: false
-          schema:
-            type: string
-            default: /
-        - name: observed-timestamp
-          in: query
-          description: observed-timestamp
-          required: false
-          schema:
-            type: string
-            example: 2021-03-21T00:10:34.030-0100
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: anchor-name
+        in: path
+        description: anchor-name
+        required: true
+        schema:
+          type: string
+          example: my-anchor
+      - name: xpath
+        in: query
+        description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html"
+        required: false
+        schema:
+          type: string
+          default: /
+        examples:
+          container xpath:
+            value: /shops/bookstore
+          list attributes xpath:
+            value: "/shops/bookstore/categories[@code=1]"
+      - name: observed-timestamp
+        in: query
+        description: observed-timestamp
+        required: false
+        schema:
+          type: string
+          example: 2021-03-21T00:10:34.030-0100
       requestBody:
         content:
           application/json:
             schema:
-              type: string
+              type: object
+            examples:
+              dataSample:
+                $ref: '#/components/examples/dataSample'
         required: true
       responses:
         "201":
@@ -584,63 +918,191 @@ paths:
             text/plain:
               schema:
                 type: string
+                example: my-resource
+        "400":
+          description: Bad Request
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 400
+                message: Bad Request
+                details: The provided request is not valid
+        "401":
+          description: Unauthorized
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 401
+                message: Unauthorized request
+                details: This request is unauthorized
+        "403":
+          description: Forbidden
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 403
+                message: Request Forbidden
+                details: This request is forbidden
+        "409":
+          description: Conflict
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 409
+                message: Conflicting request
+                details: The request cannot be processed as the resource is in use.
+        "500":
+          description: Internal Server Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 500
+                message: Internal Server Error
+                details: Internal Server Error occurred
+    delete:
+      tags:
+      - cps-data
+      summary: Delete a data node
+      description: Delete a datanode for a given dataspace and anchor given a node
+        xpath.
+      operationId: deleteDataNode
+      parameters:
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: anchor-name
+        in: path
+        description: anchor-name
+        required: true
+        schema:
+          type: string
+          example: my-anchor
+      - name: xpath
+        in: query
+        description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html"
+        required: false
+        schema:
+          type: string
+          default: /
+        examples:
+          container xpath:
+            value: /shops/bookstore
+          list attributes xpath:
+            value: "/shops/bookstore/categories[@code=1]"
+      - name: observed-timestamp
+        in: query
+        description: observed-timestamp
+        required: false
+        schema:
+          type: string
+          example: 2021-03-21T00:10:34.030-0100
+      responses:
+        "204":
+          description: No Content
+          content: {}
         "400":
           description: Bad Request
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 400
+                message: Bad Request
+                details: The provided request is not valid
         "401":
           description: Unauthorized
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 401
+                message: Unauthorized request
+                details: This request is unauthorized
         "403":
           description: Forbidden
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 403
+                message: Request Forbidden
+                details: This request is forbidden
+        "500":
+          description: Internal Server Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 500
+                message: Internal Server Error
+                details: Internal Server Error occurred
     patch:
       tags:
-        - cps-data
+      - cps-data
       summary: Update node leaves
       description: Update a data node leaves for a given dataspace and anchor and
         a parent node xpath
       operationId: updateNodeLeaves
       parameters:
-        - name: dataspace-name
-          in: path
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-        - name: anchor-name
-          in: path
-          description: anchor-name
-          required: true
-          schema:
-            type: string
-        - name: xpath
-          in: query
-          description: xpath
-          required: false
-          schema:
-            type: string
-            default: /
-        - name: observed-timestamp
-          in: query
-          description: observed-timestamp
-          required: false
-          schema:
-            type: string
-            example: 2021-03-21T00:10:34.030-0100
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: anchor-name
+        in: path
+        description: anchor-name
+        required: true
+        schema:
+          type: string
+          example: my-anchor
+      - name: xpath
+        in: query
+        description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html"
+        required: false
+        schema:
+          type: string
+          default: /
+        examples:
+          container xpath:
+            value: /shops/bookstore
+          list attributes xpath:
+            value: "/shops/bookstore/categories[@code=1]"
+      - name: observed-timestamp
+        in: query
+        description: observed-timestamp
+        required: false
+        schema:
+          type: string
+          example: 2021-03-21T00:10:34.030-0100
       requestBody:
         content:
           application/json:
             schema:
-              type: string
+              type: object
+            examples:
+              dataSample:
+                $ref: '#/components/examples/dataSample'
         required: true
       responses:
         "200":
@@ -649,129 +1111,195 @@ paths:
             application/json:
               schema:
                 type: object
-              example:
-                key: value
+              examples:
+                dataSample:
+                  value: ""
         "400":
           description: Bad Request
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 400
+                message: Bad Request
+                details: The provided request is not valid
         "401":
           description: Unauthorized
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 401
+                message: Unauthorized request
+                details: This request is unauthorized
         "403":
           description: Forbidden
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 403
+                message: Request Forbidden
+                details: This request is forbidden
+        "500":
+          description: Internal Server Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 500
+                message: Internal Server Error
+                details: Internal Server Error occurred
   /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/list-nodes:
     put:
       tags:
-        - cps-data
-      summary: Replace list-node child element(s) under existing parent node
-      description: Replace list-node child elements under existing node for a given
-        anchor and dataspace
-      operationId: replaceListNodeElements
+      - cps-data
+      summary: Replace list content
+      description: "Replace list content under a given parent, anchor and dataspace"
+      operationId: replaceListContent
       parameters:
-        - name: dataspace-name
-          in: path
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-        - name: anchor-name
-          in: path
-          description: anchor-name
-          required: true
-          schema:
-            type: string
-        - name: xpath
-          in: query
-          description: xpath
-          required: true
-          schema:
-            type: string
-        - name: observed-timestamp
-          in: query
-          description: observed-timestamp
-          required: false
-          schema:
-            type: string
-            example: 2021-03-21T00:10:34.030-0100
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: anchor-name
+        in: path
+        description: anchor-name
+        required: true
+        schema:
+          type: string
+          example: my-anchor
+      - name: xpath
+        in: query
+        description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html"
+        required: true
+        schema:
+          type: string
+        examples:
+          container xpath:
+            value: /shops/bookstore
+          list attributes xpath:
+            value: "/shops/bookstore/categories[@code=1]"
+      - name: observed-timestamp
+        in: query
+        description: observed-timestamp
+        required: false
+        schema:
+          type: string
+          example: 2021-03-21T00:10:34.030-0100
       requestBody:
         content:
           application/json:
             schema:
-              type: string
+              type: object
+            examples:
+              dataSample:
+                $ref: '#/components/examples/dataSample'
         required: true
       responses:
         "200":
-          description: Created
+          description: OK
           content:
-            text/plain:
+            application/json:
               schema:
-                type: string
+                type: object
+              examples:
+                dataSample:
+                  value: ""
         "400":
           description: Bad Request
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 400
+                message: Bad Request
+                details: The provided request is not valid
         "401":
           description: Unauthorized
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 401
+                message: Unauthorized request
+                details: This request is unauthorized
         "403":
           description: Forbidden
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 403
+                message: Request Forbidden
+                details: This request is forbidden
+        "500":
+          description: Internal Server Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 500
+                message: Internal Server Error
+                details: Internal Server Error occurred
     post:
       tags:
-        - cps-data
-      summary: Add list-node child element(s) under existing parent node
-      description: Add list-node child elements to existing node for a given anchor
-        and dataspace
-      operationId: addListNodeElements
+      - cps-data
+      summary: Add list element(s)
+      description: Add list element(s) to a list for a given anchor and dataspace
+      operationId: addListElements
       parameters:
-        - name: dataspace-name
-          in: path
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-        - name: anchor-name
-          in: path
-          description: anchor-name
-          required: true
-          schema:
-            type: string
-        - name: xpath
-          in: query
-          description: xpath
-          required: true
-          schema:
-            type: string
-        - name: observed-timestamp
-          in: query
-          description: observed-timestamp
-          required: false
-          schema:
-            type: string
-            example: 2021-03-21T00:10:34.030-0100
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: anchor-name
+        in: path
+        description: anchor-name
+        required: true
+        schema:
+          type: string
+          example: my-anchor
+      - name: xpath
+        in: query
+        description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html"
+        required: true
+        schema:
+          type: string
+        examples:
+          container xpath:
+            value: /shops/bookstore
+          list attributes xpath:
+            value: "/shops/bookstore/categories[@code=1]"
+      - name: observed-timestamp
+        in: query
+        description: observed-timestamp
+        required: false
+        schema:
+          type: string
+          example: 2021-03-21T00:10:34.030-0100
       requestBody:
         content:
           application/json:
             schema:
-              type: string
+              type: object
+            examples:
+              dataSample:
+                $ref: '#/components/examples/dataSample'
         required: true
       responses:
         "201":
@@ -780,57 +1308,86 @@ paths:
             text/plain:
               schema:
                 type: string
+                example: my-resource
         "400":
           description: Bad Request
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 400
+                message: Bad Request
+                details: The provided request is not valid
         "401":
           description: Unauthorized
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 401
+                message: Unauthorized request
+                details: This request is unauthorized
         "403":
           description: Forbidden
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 403
+                message: Request Forbidden
+                details: This request is forbidden
+        "500":
+          description: Internal Server Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 500
+                message: Internal Server Error
+                details: Internal Server Error occurred
     delete:
       tags:
-        - cps-data
-      summary: Delete list-node child element(s) under existing parent node
-      description: Delete list-node child elements under existing node for a given
-        anchor and dataspace
-      operationId: deleteListNodeElements
+      - cps-data
+      summary: Delete one or all list element(s)
+      description: Delete one or all list element(s) for a given anchor and dataspace
+      operationId: deleteListOrListElement
       parameters:
-        - name: dataspace-name
-          in: path
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-        - name: anchor-name
-          in: path
-          description: anchor-name
-          required: true
-          schema:
-            type: string
-        - name: xpath
-          in: query
-          description: xpath
-          required: true
-          schema:
-            type: string
-        - name: observed-timestamp
-          in: query
-          description: observed-timestamp
-          required: false
-          schema:
-            type: string
-            example: 2021-03-21T00:10:34.030-0100
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: anchor-name
+        in: path
+        description: anchor-name
+        required: true
+        schema:
+          type: string
+          example: my-anchor
+      - name: xpath
+        in: query
+        description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html"
+        required: true
+        schema:
+          type: string
+        examples:
+          container xpath:
+            value: /shops/bookstore
+          list attributes xpath:
+            value: "/shops/bookstore/categories[@code=1]"
+      - name: observed-timestamp
+        in: query
+        description: observed-timestamp
+        required: false
+        schema:
+          type: string
+          example: 2021-03-21T00:10:34.030-0100
       responses:
         "204":
           description: No Content
@@ -841,52 +1398,83 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 400
+                message: Bad Request
+                details: The provided request is not valid
         "401":
           description: Unauthorized
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 401
+                message: Unauthorized request
+                details: This request is unauthorized
         "403":
           description: Forbidden
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 403
+                message: Request Forbidden
+                details: This request is forbidden
+        "500":
+          description: Internal Server Error
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 500
+                message: Internal Server Error
+                details: Internal Server Error occurred
+      deprecated: true
   /v1/dataspaces/{dataspace-name}/anchors/{anchor-name}/nodes/query:
     get:
       tags:
-        - cps-query
+      - cps-query
       summary: Query data nodes
       description: Query data nodes for the given dataspace and anchor using CPS path
       operationId: getNodesByDataspaceAndAnchorAndCpsPath
       parameters:
-        - name: dataspace-name
-          in: path
-          description: dataspace-name
-          required: true
-          schema:
-            type: string
-        - name: anchor-name
-          in: path
-          description: anchor-name
-          required: true
-          schema:
-            type: string
-        - name: cps-path
-          in: query
-          description: cps-path
-          required: false
-          schema:
-            type: string
-            default: /
-        - name: include-descendants
-          in: query
-          description: include-descendants
-          required: false
-          schema:
-            type: boolean
-            default: false
+      - name: dataspace-name
+        in: path
+        description: dataspace-name
+        required: true
+        schema:
+          type: string
+          example: my-dataspace
+      - name: anchor-name
+        in: path
+        description: anchor-name
+        required: true
+        schema:
+          type: string
+          example: my-anchor
+      - name: cps-path
+        in: query
+        description: "For more details on cps path, please refer https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html"
+        required: false
+        schema:
+          type: string
+          default: /
+        examples:
+          container cps path:
+            value: //bookstore
+          list attributes cps path:
+            value: "//categories[@code=1]"
+      - name: include-descendants
+        in: query
+        description: include-descendants
+        required: false
+        schema:
+          type: boolean
+          example: false
+          default: false
       responses:
         "200":
           description: OK
@@ -894,32 +1482,49 @@ paths:
             application/json:
               schema:
                 type: object
-              example:
-                key: value
+              examples:
+                dataSample:
+                  $ref: '#/components/examples/dataSample'
         "400":
           description: Bad Request
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 400
+                message: Bad Request
+                details: The provided request is not valid
         "401":
           description: Unauthorized
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 401
+                message: Unauthorized request
+                details: This request is unauthorized
         "403":
           description: Forbidden
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
-        "404":
-          description: The specified resource was not found
+              example:
+                status: 403
+                message: Request Forbidden
+                details: This request is forbidden
+        "500":
+          description: Internal Server Error
           content:
             application/json:
               schema:
                 $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 500
+                message: Internal Server Error
+                details: Internal Server Error occurred
       x-codegen-request-body-name: xpath
 components:
   schemas:
@@ -929,29 +1534,26 @@ components:
       properties:
         status:
           type: string
-          example: "400"
         message:
           type: string
-          example: Dataspace not found
         details:
           type: string
-          example: Dataspace with name D1 does not exist.
     AnchorDetails:
       title: Anchor details by anchor Name
       type: object
       properties:
         name:
           type: string
-          example: my_anchor
+          example: my-anchor
         dataspaceName:
           type: string
-          example: my_dataspace
+          example: my-dataspace
         schemaSetName:
           type: string
-          example: my_schema_set
+          example: my-schema-set
     MultipartFile:
       required:
-        - file
+      - file
       type: object
       properties:
         file:
@@ -960,28 +1562,40 @@ components:
           format: binary
     SchemaSetDetails:
       title: Schema set details by dataspace and schemasetName
+      required:
+      - moduleReferences
       type: object
       properties:
         dataspaceName:
           type: string
-          example: my_dataspace
+          example: my-dataspace
         moduleReferences:
           type: array
           items:
             $ref: '#/components/schemas/ModuleReferences'
         name:
           type: string
-          example: my_schema_set
+          example: my-schema-set
     ModuleReferences:
       title: Module reference object
       type: object
       properties:
         name:
           type: string
-          example: module_reference_name
+          example: my-module-reference-name
         namespace:
           type: string
-          example: module_reference_namespace
+          example: my-module-reference-namespace
         revision:
           type: string
-          example: module_reference_revision
+          example: my-module-reference-revision
+  examples:
+    dataSample:
+      value:
+        test:bookstore:
+          bookstore-name: Chapters
+          categories:
+          - code: 1
+            name: SciFi
+          - code: 2
+            name: kids
index 154a441..30896f6 100644 (file)
@@ -86,23 +86,16 @@ components:
             $ref: '#/components/schemas/RestInputCmHandle'
         updatedCmHandles:
           type: array
-          example:
-            cmHandle: my-cm-handle
-            cmHandleProperties:
-              add-my-property: add-property
-              update-my-property: updated-property
-              delete-my-property: ~
-            publicCmHandleProperties:
-              add-my-property: add-property
-              update-my-property: updated-property
-              delete-my-property: ~
           items:
             $ref: '#/components/schemas/RestInputCmHandle'
         removedCmHandles:
           type: array
+          example:
+          - my-cm-handle1
+          - my-cm-handle2
+          - my-cm-handle3
           items:
             type: string
-            example: "[\"my-cm-handle1\",\"my-cm-handle2\",\"my-cm-handle3\"]"
     RestInputCmHandle:
       required:
       - cmHandle
index b7a6563..5a6a600 100644 (file)
@@ -70,6 +70,17 @@ paths:
           sample 3:
             value:
               options: "(depth=2,fields=book/authors)"
+      - name: topic
+        in: query
+        description: topic parameter in query.
+        required: false
+        allowReserved: true
+        schema:
+          type: string
+        examples:
+          sample 1:
+            value:
+              topic: my-topic-name
       responses:
         "200":
           description: OK
@@ -184,6 +195,17 @@ paths:
           sample 3:
             value:
               options: "(depth=2,fields=book/authors)"
+      - name: topic
+        in: query
+        description: topic parameter in query.
+        required: false
+        allowReserved: true
+        schema:
+          type: string
+        examples:
+          sample 1:
+            value:
+              topic: my-topic-name
       responses:
         "200":
           description: OK
@@ -664,7 +686,7 @@ paths:
               schema:
                 type: array
                 items:
-                  $ref: '#/components/schemas/ModuleReference'
+                  $ref: '#/components/schemas/RestModuleReference'
         "400":
           description: Bad Request
           content:
@@ -851,7 +873,7 @@ components:
           type: string
         details:
           type: string
-    ModuleReference:
+    RestModuleReference:
       title: Module reference details
       type: object
       properties:
index bc46681..e8a75d9 100644 (file)
@@ -1,6 +1,6 @@
 .. This work is licensed under a Creative Commons Attribution 4.0 International License.
 .. http://creativecommons.org/licenses/by/4.0
-.. Copyright (C) 2021 Nordix Foundation
+.. Copyright (C) 2021-2022 Nordix Foundation
 
 .. DO NOT CHANGE THIS LABEL FOR RELEASE NOTES - EVEN THOUGH IT GIVES A WARNING
 .. _design:
@@ -20,17 +20,137 @@ The CPS path parameter is used for querying xpaths. CPS path is inspired by the
 
 This section describes the functionality currently supported by CPS Path.
 
-Sample Data
-===========
+Sample Yang Model
+=================
 
-The xml below describes some basic data to be used to illustrate the CPS Path functionality.
+.. code-block::
+
+  module stores {
+      yang-version 1.1;
+      namespace "org:onap:ccsdk:sample";
+
+      prefix book-store;
+
+      revision "2020-09-15" {
+          description
+            "Sample Model";
+      }
+      container shops {
+
+          container bookstore {
+
+              leaf bookstore-name {
+                  type string;
+              }
+
+              leaf name {
+                  type string;
+              }
+
+              list categories {
+
+                  key "code";
+
+                  leaf code {
+                      type uint16;
+                  }
+
+                  leaf name {
+                      type string;
+                  }
+
+                  leaf numberOfBooks {
+                      type uint16;
+                  }
+
+                  container books {
+
+                      list book {
+                          key title;
+
+                          leaf title {
+                              type string;
+                          }
+                          leaf price {
+                              type uint16;
+                          }
+                          leaf-list label {
+                              type string;
+                          }
+                          leaf-list edition {
+                              type string;
+                          }
+                      }
+                  }
+              }
+          }
+      }
+  }
+
+**Note.** 'categories' is a Yang List and 'code' is its key leaf. All other data nodes are Yang Containers. 'label' and 'edition' are both leaf-lists.
+
+**Note.** CPS accepts only json data. The xml data presented here is for illustration purposes only.
+
+The json and xml below describes some basic data to be used to illustrate the CPS Path functionality.
+
+Sample Data in Json
+===================
+
+.. code-block:: json
+
+    {
+      "shops": {
+        "bookstore": {
+          "bookstore-name": "Chapters",
+          "name": "Chapters",
+          "categories": [
+            {
+              "code": 1,
+              "name": "SciFi",
+              "numberOfBooks": 2,
+              "books": {
+                "book": [
+                  {
+                    "title": "2001: A Space Odyssey",
+                    "price": 5,
+                    "label": ["sale", "classic"],
+                    "edition": ["1968", "2018"]
+                  },
+                  {
+                    "title": "Dune",
+                    "price": 5,
+                    "label": ["classic"],
+                    "edition": ["1965"]
+                  }
+                ]
+              }
+            },
+            {
+              "code": 2,
+              "name": "Kids",
+              "numberOfBooks": 1,
+              "books": {
+                "book": [
+                  {
+                    "title": "Matilda"
+                  }
+                ]
+              }
+            }
+          ]
+        }
+      }
+    }
+
+Sample Data in XML
+==================
 
 .. code-block:: xml
 
     <shops>
        <bookstore name="Chapters">
           <bookstore-name>Chapters</bookstore-name>
-          <categories code="1" name="SciFi" numberOfBooks="2">
+          <categories code=1 name="SciFi" numberOfBooks="2">
              <books>
                 <book title="2001: A Space Odyssey" price="5">
                    <label>sale</label>
@@ -44,7 +164,7 @@ The xml below describes some basic data to be used to illustrate the CPS Path fu
                 </book>
              </books>
           </categories>
-          <categories code="2" name="Kids" numberOfBooks="1">
+          <categories code=2 name="Kids" numberOfBooks="1">
              <books>
                 <book title="Matilda" />
              </books>
@@ -52,8 +172,6 @@ The xml below describes some basic data to be used to illustrate the CPS Path fu
        </bookstore>
     </shops>
 
-**Note.** 'categories' is a Yang List and 'code' is its key leaf. All other data nodes are Yang Containers. 'label' and 'edition' are both leaf-lists.
-
 General Notes
 =============
 
@@ -79,12 +197,14 @@ absolute-path
 
 **Examples**
   - ``/shops/bookstore``
-  - ``/shops/bookstore/categories[@code=1]``
-  - ``/shops/bookstore/categories[@code=1]/book``
+  - ``/shops/bookstore/categories[@code='1']/books``
+  - ``/shops/bookstore/categories[@code='1']/books/book[@title='2001: A Space Odyssey']``
 
 **Limitations**
   - Absolute paths must start with the top element (data node) as per the model tree.
   - Each list reference must include a valid instance reference to the key for that list. Except when it is the last element.
+  - The Absolute path to list with integer key will not work. It needs to be surrounded with a single quote ([@code='1'])
+    as if it is a string. This will be fixed in `CPS-961 <https://jira.onap.org/browse/CPS-961>`_
 
 descendant-path
 ---------------
@@ -95,7 +215,7 @@ descendant-path
 
 **Examples**
   - ``//bookstore``
-  - ``//categories[@code=1]/book``
+  - ``//categories[@code='1']/books``
   - ``//bookstore/categories``
 
 **Limitations**
@@ -113,7 +233,7 @@ leaf-conditions
   - ``/shops/bookstore/categories[@numberOfBooks=1]``
   - ``//categories[@name="Kids"]``
   - ``//categories[@name='Kids']``
-  - ``//categories[@code=1]/books/book[@title='Dune' and @price=5]``
+  - ``//categories[@code='1']/books/book[@title='Dune' and @price=5]``
 
 **Limitations**
   - Only the last list or container can be queried leaf values. Any ancestor list will have to be referenced by its key name-value pair(s).
@@ -156,9 +276,9 @@ The ancestor axis can be added to any CPS path query but has to be the last part
 
 **Examples**
   - ``//book/ancestor::categories``
-  - ``//categories[@genre="SciFi"]/book/ancestor::bookstore``
-  - ``book/ancestor::categories[@code=1]/books``
-  - ``//book/label[text()="classic"]/ancestor::shop``
+  - ``//categories[@code='2']/books/ancestor::bookstore``
+  - ``//book/ancestor::categories[@code='1']/books``
+  - ``//book/label[text()="classic"]/ancestor::shops``
 
 **Limitations**
   - Ancestor list elements can only be addressed using the list key leaf.
index 2f68a64..46160c4 100644 (file)
@@ -7,13 +7,13 @@
 .. _deployment:
 
 CPS Deployment
-==============
+##############
 
 .. contents::
     :depth: 2
 
 CPS OOM Charts
---------------
+==============
 The CPS kubernetes chart is located in the `OOM repository <https://github.com/onap/oom/tree/master/kubernetes/cps>`_.
 This chart includes different cps components referred as <cps-component-name> further in the document are listed below:
 
@@ -26,7 +26,8 @@ This chart includes different cps components referred as <cps-component-name> fu
 Please refer to the `OOM documentation <https://docs.onap.org/projects/onap-oom/en/latest/oom_user_guide.html>`_ on how to install and deploy ONAP.
 
 Installing or Upgrading CPS Components
---------------------------------------
+======================================
+
 The assumption is you have cloned the charts from the OOM repository into a local directory.
 
 **Step 1** Go to the cps charts and edit properties in values.yaml files to make any changes to particular cps component if required.
@@ -91,7 +92,7 @@ After deploying cps, keep monitoring the cps pods until they come up.
   kubectl get pods -n <namespace> | grep <cps-component-name>
 
 Restarting a faulty component
------------------------------
+=============================
 Each cps component can be restarted independently by issuing the following command:
 
 .. code-block:: bash
@@ -102,7 +103,7 @@ Each cps component can be restarted independently by issuing the following comma
 .. _cps_common_credentials_retrieval:
 
 Credentials Retrieval
----------------------
+=====================
 
 Application and database credentials are kept in Kubernetes secrets. They are defined as external secrets in the
 values.yaml file to be used across different components as :
@@ -161,8 +162,9 @@ Additional Cps-Core Customizations
 ==================================
 
 The following table lists some properties that can be specified as Helm chart
-values to configure the application to be deployed. This list is not
-exhaustive.
+values to configure the application to be deployed. This list is not exhaustive.
+
+Any spring supported property can be configured by providing in ``config.additional.<spring-supported-property-name>: value`` Example: config.additional.spring.datasource.hikari.maximumPoolSize: 30
 
 +---------------------------------------+---------------------------------------------------------------------------------------------------------+-------------------------------+
 | Property                              | Description                                                                                             | Default Value                 |
@@ -277,6 +279,10 @@ exhaustive.
 | notification.async.executor.          |                                                                                                         |                               |
 | thread-name-prefix                    |                                                                                                         |                               |
 +---------------------------------------+---------------------------------------------------------------------------------------------------------+-------------------------------+
+| config.additional.                    | Specifies number of database connections between database and application.                              | ``10``                        |
+| spring.datasource.hikari.             | This property controls the maximum size that the pool is allowed to reach,                              |                               |
+| maximumPoolSize                       | including both idle and in-use connections.                                                             |                               |
++---------------------------------------+---------------------------------------------------------------------------------------------------------+-------------------------------+
 
 CPS-Core Docker Installation
 ============================
index 62ba5e8..eaf3646 100755 (executable)
@@ -9,7 +9,10 @@
 .. _cps-framework-doc:
 
 CPS Documentation
------------------
+#################
+
+CPS Core
+========
 
 .. toctree::
    :maxdepth: 1
@@ -22,12 +25,12 @@ CPS Documentation
    deployment.rst
    release-notes.rst
 
-DMI-Plugin Documentation
-------------------------
+DMI-Plugin
+==========
 
 * :ref:`DMI-Plugin<onap-cps-ncmp-dmi-plugin:master_index>`
 
-CPS-Temporal Documentation
---------------------------
+CPS Temporal
+============
 
 * :ref:`CPS-Temporal<onap-cps-cps-temporal:master_index>`
index 4b69dd8..cde6f6d 100644 (file)
@@ -4,7 +4,7 @@
 .. _overview:
 
 CPS Overview
-============
+############
 
 The Configuration Persistence Service (CPS) is a platform component that is designed to serve as a
 data repository for runtime data that needs persistence.
@@ -28,10 +28,10 @@ Types of data that is stored:
   configuration and operational parameters depending on how they are used.
 
 CPS Components
---------------
+==============
 
 CPS-Core
-########
+--------
 This is the component of CPS which encompasses the generic storage of Yang module data.
 
 **NCMP**
@@ -43,13 +43,13 @@ NCMP accesses all network Data-Model-Inventory (DMI) information via NCMP-DMI-Pl
 even though CPS-Core could be deployed without the NCMP extension.
 
 NCMP-DMI-Plugin
-####################
+---------------
 
 The Data-Model-Inventory (DMI) Plugin is a rest interface used to synchronize CM-Handles data between CPS and DMI through the DMI-Plugin.
 This is built previously from the CPS-NF-Proxy component.
 
 CPS-Temporal
-############
+------------
 
 This service is responsible to provide a time oriented perspective for
 operational network data. It provides features to store and retrieve sequences
@@ -57,14 +57,8 @@ of configurations or states along with the associated times when they occurred
 or have been observed.
 
 CPS Project
------------
-
-Wiki: `Configuration Persistence Service Project <https://wiki.onap.org/display/DW/Configuration+Persistence+Service+Project>`_
-
-Contact Information
--------------------
-
-onap-discuss@lists.onap.org
+===========
 
-Meeting details `Join  <https://zoom.us/j/836561560?pwd=TTZNcFhXTWYxMmZ4SlgzcVZZQXluUT09>`_
-`Agenda <https://wiki.onap.org/pages/viewpage.action?pageId=111117075>`_
+* Wiki: `Configuration Persistence Service Project <https://wiki.onap.org/display/DW/Configuration+Persistence+Service+Project>`_
+* Contact Information: onap-discuss@lists.onap.org
+* Meeting details: `Join  <https://zoom.us/j/836561560?pwd=TTZNcFhXTWYxMmZ4SlgzcVZZQXluUT09>`_ & `Agenda <https://wiki.onap.org/pages/viewpage.action?pageId=111117075>`_
index 0ca0547..2fea4a2 100755 (executable)
@@ -5,11 +5,8 @@
 .. DO NOT CHANGE THIS LABEL FOR RELEASE NOTES - EVEN THOUGH IT GIVES A WARNING
 .. _release_notes:
 
-
-
-=================
 CPS Release Notes
-=================
+#################
 
 .. contents::
     :depth: 2
@@ -19,10 +16,25 @@ CPS Release Notes
 ..      * * *   JAKARTA   * * *
 ..      ========================
 
-Version: 3.0.0-SNAPSHOT
-=======================
+Version: 3.0.0
+==============
 
-This section lists the main changes & fixes merged into master (snapshot) version of CPS-NCMP. This information is here to assist developers that want experiment/test using our latest code bases directly. Stability of this is not guaranteed.
+Release Data
+------------
+
++--------------------------------------+--------------------------------------------------------+
+| **CPS Project**                      |                                                        |
+|                                      |                                                        |
++--------------------------------------+--------------------------------------------------------+
+| **Docker images**                    | onap/cps-and-ncmp:3.0.0                                |
+|                                      |                                                        |
++--------------------------------------+--------------------------------------------------------+
+| **Release designation**              | 3.0.0 Jakarta                                          |
+|                                      |                                                        |
++--------------------------------------+--------------------------------------------------------+
+| **Release date**                     | 2022 March 15                                          |
+|                                      |                                                        |
++--------------------------------------+--------------------------------------------------------+
 
 Features
 --------
@@ -33,6 +45,8 @@ Features
    - `CPS-741 <https://jira.onap.org/browse/CPS-741>`_  Re sync after removing cm handles
    - `CPS-777 <https://jira.onap.org/browse/CPS-777>`_  Ensure all DMI operations use POST method
    - `CPS-780 <https://jira.onap.org/browse/CPS-780>`_  Add examples for parameters, request and response in openapi yaml for cps-core
+   - `CPS-789 <https://jira.onap.org/browse/CPS-789>`_ CPS Data Updated Event Schema V2 to support delete operation
+   - `CPS-791 <https://jira.onap.org/browse/CPS-791>`_ CPS-Core sends delete notification event
    - `CPS-817 <https://jira.onap.org/browse/CPS-817>`_  Create Endpoint For Get Cm Handles (incl. public properties) By Name
    - `CPS-837 <https://jira.onap.org/browse/CPS-837>`_  Add Remove and Update properties (DMI and Public) as part of CM Handle Registration update
 
@@ -59,6 +73,9 @@ Null can no longer be passed within the dmi plugin service names when registerin
 `CPS-837 <https://jira.onap.org/browse/CPS-837>`_ null is now used to indicate if a property should be removed as part
 of cm handle registration.
 
+The Absolute path to list with integer key will not work. Please refer `CPS-961 <https://jira.onap.org/browse/CPS-961>`_
+for more information.
+
 *Known Vulnerabilities*
 
 None
index 6c2729c..d1181d3 100644 (file)
@@ -5,7 +5,7 @@
     <parent>
         <groupId>org.onap.cps</groupId>
         <artifactId>cps-parent</artifactId>
-        <version>3.0.0-SNAPSHOT</version>
+        <version>3.1.0-SNAPSHOT</version>
         <relativePath>../cps-parent/pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
                 <groupId>org.jacoco</groupId>
                 <artifactId>jacoco-maven-plugin</artifactId>
                 <configuration>
+                <!--All exclusions below are referring to generated code-->
                     <excludes>
                         <exclude>org/onap/cps/event/model/*</exclude>
                         <exclude>org/onap/cps/rest/model/*</exclude>
                         <exclude>org/onap/cps/cpspath/parser/antlr4/*</exclude>
                         <exclude>org/onap/cps/ncmp/rest/model/*</exclude>
+                        <exclude>org/onap/cps/ncmp/rest/controller/*MapperImpl.class</exclude>
+                        <exclude>org/onap/cps/rest/controller/*MapperImpl.class</exclude>
                     </excludes>
                 </configuration>
                 <executions>
diff --git a/pom.xml b/pom.xml
index 87398bd..23ef44b 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -32,7 +32,7 @@
 \r
     <groupId>org.onap.cps</groupId>\r
     <artifactId>cps-aggregator</artifactId>\r
-    <version>3.0.0-SNAPSHOT</version>\r
+    <version>3.1.0-SNAPSHOT</version>\r
     <packaging>pom</packaging>\r
 \r
     <name>cps</name>\r
diff --git a/releases/3.0.0-container.yaml b/releases/3.0.0-container.yaml
new file mode 100644 (file)
index 0000000..f227bdb
--- /dev/null
@@ -0,0 +1,8 @@
+distribution_type: container
+container_release_tag: 3.0.0
+project: cps
+log_dir: cps-maven-docker-stage-master/504/
+ref: a1129b696f3197fc7d8a3b63bcd84b5b2dd8874e
+containers:
+  - name: 'cps-and-ncmp'
+    version: '3.0.0-20220315T180237Z'
diff --git a/releases/3.0.0.yaml b/releases/3.0.0.yaml
new file mode 100644 (file)
index 0000000..60dd811
--- /dev/null
@@ -0,0 +1,4 @@
+distribution_type: maven
+log_dir: cps-maven-stage-master/504/
+project: cps
+version: 3.0.0
index 50cef48..df033a3 100644 (file)
@@ -25,7 +25,7 @@
     <modelVersion>4.0.0</modelVersion>
     <groupId>org.onap.cps</groupId>
     <artifactId>spotbugs</artifactId>
-    <version>3.0.0-SNAPSHOT</version>
+    <version>3.1.0-SNAPSHOT</version>
 
     <properties>
         <nexusproxy>https://nexus.onap.org</nexusproxy>
         <snapshotNexusPath>/content/repositories/snapshots/</snapshotNexusPath>
     </properties>
 
+    <build>
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-deploy-plugin</artifactId>
+                    <version>2.8.2</version>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+    </build>
+
     <distributionManagement>
         <repository>
             <id>ecomp-releases</id>
index 17f2daa..870b994 100755 (executable)
@@ -1,5 +1,5 @@
 #  ============LICENSE_START=======================================================
-#  Copyright (C) 2021 Nordix Foundation
+#  Copyright (C) 2021-2022 Nordix Foundation
 #  Modifications Copyright (C) 2022 Bell Canada.
 #  ================================================================================
 #  Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,7 +21,7 @@
 # because they are used in Jenkins, whose plug-in doesn't support this
 
 major=3
-minor=0
+minor=1
 patch=0
 
 base_version=${major}.${minor}.${patch}