Merge "Xpath to NodeId invalid"
authoraditya puthuparambil <aditya.puthuparambil@bell.ca>
Tue, 20 Sep 2022 08:47:02 +0000 (08:47 +0000)
committerGerrit Code Review <gerrit@onap.org>
Tue, 20 Sep 2022 08:47:02 +0000 (08:47 +0000)
70 files changed:
INFO.yaml
cps-application/src/main/resources/application.yml
cps-ncmp-rest-stub/src/main/java/org/onap/cps/ncmp/rest/stub/controller/NetworkCmProxyStubController.java
cps-ncmp-rest/docs/openapi/components.yaml
cps-ncmp-rest/docs/openapi/ncmp.yml
cps-ncmp-rest/docs/openapi/openapi.yml
cps-ncmp-rest/pom.xml
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/DatastoreType.java [new file with mode: 0644]
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreOperationalResourceRequestHandler.java [new file with mode: 0644]
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughOperationalResourceRequestHandler.java [new file with mode: 0644]
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughRunningResourceRequestHandler.java [new file with mode: 0644]
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandler.java [new file with mode: 0644]
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandlerFactory.java [new file with mode: 0644]
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/InvalidDatastoreException.java [new file with mode: 0644]
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/InvalidTopicException.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/InvalidTopicException.java with 96% similarity]
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandler.java
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/TopicValidator.java [new file with mode: 0644]
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/handlers/NcmpDatastoreResourceRequestHandlerFactorySpec.groovy [new file with mode: 0644]
cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/exceptions/NetworkCmProxyRestExceptionHandlerSpec.groovy
cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/util/TopicValidatorSpec.groovy [new file with mode: 0644]
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/client/DmiRestClient.java
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/inventory/CmHandleQueries.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CmHandleQueriesImpl.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistence.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImpl.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncService.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncTasks.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdog.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponse.java
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyCmHandlerQueryServiceSpec.groovy
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/inventory/CmHandleQueriesImplSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/CmHandleQueriesSpec.groovy with 98% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImplSpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/InventoryPersistenceSpec.groovy with 99% similarity]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncServiceSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncTasksSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/ModuleSyncWatchdogSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/inventory/sync/SyncUtilsSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/models/CmHandleRegistrationResponseSpec.groovy
cps-rest/src/main/java/org/onap/cps/rest/exceptions/CpsRestExceptionHandler.java
cps-ri/src/main/java/org/onap/cps/spi/impl/CpsDataPersistenceServiceImpl.java
cps-ri/src/main/java/org/onap/cps/spi/impl/CpsModulePersistenceServiceImpl.java
cps-ri/src/main/java/org/onap/cps/spi/repository/FragmentRepositoryCpsPathQueryImpl.java
cps-ri/src/main/java/org/onap/cps/spi/repository/YangResourceNativeRepositoryImpl.java
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceQueryDataNodeSpec.groovy
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceIntegrationSpec.groovy
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsDataPersistenceServiceSpec.groovy
cps-ri/src/test/groovy/org/onap/cps/spi/impl/CpsModulePersistenceServiceIntegrationSpec.groovy
cps-ri/src/test/resources/data/cps-path-query.sql
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/spi/CpsDataPersistenceService.java
cps-service/src/main/java/org/onap/cps/spi/CpsModulePersistenceService.java
cps-service/src/main/java/org/onap/cps/spi/exceptions/AlreadyDefinedExceptionBatch.java [new file with mode: 0644]
cps-service/src/main/java/org/onap/cps/spi/exceptions/ConcurrencyException.java
cps-service/src/main/java/org/onap/cps/utils/CpsValidator.java
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy
cps-service/src/test/groovy/org/onap/cps/utils/CpsValidatorSpec.groovy
docs/api/swagger/cps/openapi.yaml
docs/api/swagger/ncmp/openapi-inventory.yaml
docs/api/swagger/ncmp/openapi.yaml
docs/release-notes.rst
releases/3.1.0-container.yaml [new file with mode: 0644]
releases/3.1.0.yaml [new file with mode: 0644]

index a411e37..ca4f250 100755 (executable)
--- a/INFO.yaml
+++ b/INFO.yaml
@@ -52,11 +52,6 @@ committers:
       company: 'Bell Canada'
       id: 'puthuparambil.aditya'
       timezone: 'Europe/Dublin'
-    - name: 'Renu Kumari'
-      email: 'renu.kumari@bell.ca'
-      company: 'Bell Canada'
-      id: 'renukumari'
-      timezone: 'America/Toronto'
     - name: 'Joseph Keenan'
       email: 'joseph.keenan@est.tech'
       company: 'Ericsson Software Technology'
index 9b6f41e..f7a06c5 100644 (file)
@@ -45,13 +45,12 @@ spring:
         username: ${DB_USERNAME}\r
         password: ${DB_PASSWORD}\r
         driverClassName: org.postgresql.Driver\r
-        initialization-mode: always\r
         hikari:\r
             minimumIdle: 5\r
             maximumPoolSize: 80\r
-            idleTimeout: 120000\r
-            connectionTimeout: 300000\r
-            leakDetectionThreshold: 300000\r
+            idleTimeout: 60000\r
+            connectionTimeout: 120000\r
+            leakDetectionThreshold: 30000\r
             pool-name: CpsDatabasePool\r
 \r
     cache:\r
@@ -91,6 +90,9 @@ spring:
       default-property-inclusion: NON_NULL\r
       serialization:\r
         FAIL_ON_EMPTY_BEANS: false\r
+    sql:\r
+      init:\r
+        mode: ALWAYS\r
 app:\r
     ncmp:\r
         async-m2m:\r
index 67af6f1..bb919b5 100644 (file)
@@ -30,10 +30,9 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
-import javax.validation.Valid;
-import javax.validation.constraints.NotNull;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.ncmp.rest.api.NetworkCmProxyApi;
+import org.onap.cps.ncmp.rest.controller.handlers.DatastoreType;
 import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters;
 import org.onap.cps.ncmp.rest.model.RestModuleDefinition;
 import org.onap.cps.ncmp.rest.model.RestModuleReference;
@@ -58,20 +57,55 @@ public class NetworkCmProxyStubController implements NetworkCmProxyApi {
     private String pathToResponseFiles;
 
     @Override
-    public ResponseEntity<Void> createResourceDataRunningForCmHandle(@NotNull @Valid final String resourceIdentifier,
-        final String cmHandleId, @Valid final Object body, final String contentType) {
+    public ResponseEntity<Object> getResourceDataForCmHandle(final String dataStoreName,
+                                                             final String cmHandle,
+                                                             final String resourceIdentifier,
+                                                             final String optionsParamInQuery,
+                                                             final String topicParamInQuery,
+                                                             final Boolean includeDescendants) {
+        if (DatastoreType.PASSTHROUGH_OPERATIONAL == DatastoreType.fromDatastoreName(dataStoreName)) {
+            final ResponseEntity<Map<String, Object>> asyncResponse = populateAsyncResponse(topicParamInQuery);
+            final Map<String, Object> asyncResponseData = asyncResponse.getBody();
+            Object responseObject = null;
+            // read JSON file and map/convert to java POJO
+            final ClassPathResource resource =
+                    new ClassPathResource(pathToResponseFiles + "passthrough-operational-example.json");
+            try (InputStream inputStream = resource.getInputStream()) {
+                final String string = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
+                final ObjectMapper mapper = new ObjectMapper();
+                responseObject = mapper.readValue(string, Object.class);
+            } catch (final IOException exception) {
+                log.error("Error reading the file.", exception);
+                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
+            }
+            if (asyncResponseData == null) {
+                return ResponseEntity.ok(responseObject);
+            }
+            return ResponseEntity.ok(asyncResponse);
+        }
+        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
+    }
+
+    @Override
+    public ResponseEntity<Void> createResourceDataRunningForCmHandle(final String datastoreName,
+                                                                     final String resourceIdentifier,
+                                                                     final String cmHandleId,
+                                                                     final Object body,
+                                                                     final String contentType) {
         return new ResponseEntity<>(HttpStatus.CREATED);
     }
 
     @Override
-    public ResponseEntity<Void> deleteResourceDataRunningForCmHandle(final String cmHandleId,
-        @NotNull @Valid final String resourceIdentifier, final String contentType) {
+    public ResponseEntity<Void> deleteResourceDataRunningForCmHandle(final String datastoreName,
+                                                                     final String cmHandleId,
+                                                                     final String resourceIdentifier,
+                                                                     final String contentType) {
         return new ResponseEntity<>(HttpStatus.NO_CONTENT);
     }
 
     @Override
     public ResponseEntity<List<RestOutputCmHandle>> searchCmHandles(
-        final CmHandleQueryParameters cmHandleQueryParameters) {
+            final CmHandleQueryParameters cmHandleQueryParameters) {
         List<RestOutputCmHandle> restOutputCmHandles = null;
         // read JSON file and map/convert to java POJO
         final ClassPathResource resource = new ClassPathResource(pathToResponseFiles + "cmHandlesSearch.json");
@@ -88,19 +122,19 @@ public class NetworkCmProxyStubController implements NetworkCmProxyApi {
 
     @Override
     public ResponseEntity<Object> setDataSyncEnabledFlagForCmHandle(final String cmHandleId,
-                                                                final Boolean dataSyncEnabled) {
+                                                                    final Boolean dataSyncEnabled) {
         return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
     }
 
     @Override
     public ResponseEntity<List<String>> searchCmHandleIds(
-        final CmHandleQueryParameters cmHandleQueryParameters) {
+            final CmHandleQueryParameters cmHandleQueryParameters) {
         return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
     }
 
     @Override
     public ResponseEntity<RestOutputCmHandlePublicProperties> getCmHandlePublicPropertiesByCmHandleId(
-        final String cmHandleId) {
+            final String cmHandleId) {
         return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
     }
 
@@ -119,48 +153,12 @@ public class NetworkCmProxyStubController implements NetworkCmProxyApi {
         return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
     }
 
-    /**
-     * Get resource data from operational datastore.
-     *
-     * @param cmHandleId cm handle identifier
-     * @param resourceIdentifier resource identifier
-     * @param optionsParamInQuery options query parameter
-     * @param topicParamInQuery topic query parameter
-     * @return {@code ResponseEntity} response from dmi plugin
-     */
-    @Override
-    public ResponseEntity<Object> getResourceDataOperationalForCmHandle(final String cmHandleId,
-        final String resourceIdentifier, final String optionsParamInQuery, final String topicParamInQuery) {
-        final ResponseEntity<Map<String, Object>> asyncResponse = populateAsyncResponse(topicParamInQuery);
-        final Map<String, Object> asyncResponseData = asyncResponse.getBody();
-        Object responseObject = null;
-        // read JSON file and map/convert to java POJO
-        final ClassPathResource resource = new ClassPathResource(pathToResponseFiles
-            + "passthrough-operational-example.json");
-        try (InputStream inputStream = resource.getInputStream()) {
-            final String string = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
-            final ObjectMapper mapper = new ObjectMapper();
-            responseObject = mapper.readValue(string, Object.class);
-        } catch (final IOException exception) {
-            log.error("Error reading the file.", exception);
-            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
-        }
-        if (asyncResponseData == null) {
-            return ResponseEntity.ok(responseObject);
-        }
-        return ResponseEntity.ok(asyncResponse);
-
-    }
-
-    @Override
-    public ResponseEntity<Object> getResourceDataRunningForCmHandle(final String cmHandleId,
-        final String resourceIdentifier, final String options, final String topic) {
-        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
-    }
-
     @Override
-    public ResponseEntity<Object> patchResourceDataRunningForCmHandle(final String resourceIdentifier,
-        final String cmHandleId, final Object body, final String contentType) {
+    public ResponseEntity<Object> patchResourceDataRunningForCmHandle(final String datastoreName,
+                                                                      final String resourceIdentifier,
+                                                                      final String cmHandleId,
+                                                                      final Object body,
+                                                                      final String contentType) {
         return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
     }
 
@@ -170,8 +168,11 @@ public class NetworkCmProxyStubController implements NetworkCmProxyApi {
     }
 
     @Override
-    public ResponseEntity<Object> updateResourceDataRunningForCmHandle(@NotNull @Valid final String resourceIdentifier,
-        final String cmHandleId, @Valid final Object body, final String contentType) {
+    public ResponseEntity<Object> updateResourceDataRunningForCmHandle(final String datastoreName,
+                                                                       final String resourceIdentifier,
+                                                                       final String cmHandleId,
+                                                                       final Object body,
+                                                                       final String contentType) {
         return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
     }
 
index 427f083..7ca09ce 100644 (file)
@@ -86,7 +86,7 @@ components:
           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 ]
     DmiPluginRegistrationErrorResponse:
       type: object
       properties:
@@ -124,14 +124,14 @@ components:
           type: string
           example: my-cm-handle
         cmHandleProperties:
-            $ref: '#/components/schemas/RestCmHandleProperties'
+          $ref: '#/components/schemas/RestCmHandleProperties'
         publicCmHandleProperties:
-            $ref: '#/components/schemas/RestCmHandleProperties'
+          $ref: '#/components/schemas/RestCmHandleProperties'
     RestCmHandleProperties:
-        type: object
-        additionalProperties:
-            type: string
-            example: my-property
+      type: object
+      additionalProperties:
+        type: string
+        example: my-property
 
     #Response Schemas
     RestModuleReference:
@@ -288,21 +288,21 @@ components:
 
   examples:
     dataSampleRequest:
-        summary: Sample request
-        description: Sample request body
-        value:
-          test:bookstore:
-            bookstore-name: Chapters
-            categories:
-              - code: '01'
-                name: SciFi
-                books:
+      summary: Sample request
+      description: Sample request body
+      value:
+        test:bookstore:
+          bookstore-name: Chapters
+          categories:
+            - code: '01'
+              name: SciFi
+              books:
                 - authors:
                     - Iain M. Banks
                     - Ursula K. Le Guin
-              - code: '02'
-                name: kids
-                books:
+            - code: '02'
+              name: kids
+              books:
                 - authors:
                     - Philip Pullman
 
@@ -351,22 +351,22 @@ components:
                             - Philip Pullman
 
     dataSampleResponse:
-        summary: Sample response
-        description: Sample response for selecting 'sample 1'.
-        value:
-          bookstore:
-            categories:
-              - code: '01'
-                books:
-                  - authors:
-                      - Iain M. Banks
-                      - Ursula K. Le Guin
-                name: SciFi
-              - code: '02'
-                books:
-                  - authors:
-                      - Philip Pullman
-                name: kids
+      summary: Sample response
+      description: Sample response for selecting 'sample 1'.
+      value:
+        bookstore:
+          categories:
+            - code: '01'
+              books:
+                - authors:
+                    - Iain M. Banks
+                    - Ursula K. Le Guin
+              name: SciFi
+            - code: '02'
+              books:
+                - authors:
+                    - Philip Pullman
+              name: kids
 
     allCmHandleQueryParameters:
       value:
@@ -448,7 +448,7 @@ components:
     includeDescendantsOptionInQuery:
       name: include-descendants
       in: query
-      description: include-descendants
+      description: Determines if descendants are included in response
       required: false
       schema:
         type: boolean
@@ -526,6 +526,14 @@ components:
         type: string
         default: application/json
         example: application/yang-data+json
+    datastoreName:
+      name: ncmp-datastore-name
+      in: path
+      description: The type of the requested data
+      required: true
+      schema:
+        type: string
+        example: ncmp-datastore:operational
 
   responses:
     NotFound:
@@ -555,9 +563,9 @@ components:
           schema:
             $ref: '#/components/schemas/ErrorMessage'
           example:
-           status: 403
-           message: Forbidden error message
-           details: Forbidden error details
+            status: 403
+            message: Forbidden error message
+            details: Forbidden error details
     BadRequest:
       description: Bad Request
       content:
@@ -565,9 +573,9 @@ components:
           schema:
             $ref: '#/components/schemas/ErrorMessage'
           example:
-           status: 400 BAD_REQUEST
-           message: Bad request error message
-           details: Bad request error details
+            status: 400 BAD_REQUEST
+            message: Bad request error message
+            details: Bad request error details
     Conflict:
       description: Conflict
       content:
@@ -575,9 +583,9 @@ components:
           schema:
             $ref: '#/components/schemas/ErrorMessage'
           example:
-           status: 409 CONFLICT
-           message: Conflict error message
-           details: Conflict error details
+            status: 409 CONFLICT
+            message: Conflict error message
+            details: Conflict error details
     NotImplemented:
       description: The given path has not been implemented
       content:
@@ -585,9 +593,9 @@ components:
           schema:
             $ref: '#/components/schemas/ErrorMessage'
           example:
-           status: 501
-           message: Not implemented error message
-           details: Not implemented error details
+            status: 501
+            message: Not implemented error message
+            details: Not implemented error details
     Ok:
       description: OK
       content:
@@ -596,10 +604,10 @@ components:
             type: object
     Created:
       description: Created
-      content: {}
+      content: { }
     NoContent:
       description: No Content
-      content: {}
+      content: { }
     InternalServerError:
       description: Internal Server Error
       content:
index 4266fc4..38db26f 100755 (executable)
 #
 #  SPDX-License-Identifier: Apache-2.0
 #  ============LICENSE_END=========================================================
-getResourceDataForPassthroughOperational:
-  get:
-    tags:
-      - network-cm-proxy
-    summary: Get resource data from pass-through operational for cm handle
-    description: Get resource data from pass-through operational for given cm handle
-    operationId: getResourceDataOperationalForCmHandle
-    parameters:
-      - $ref: 'components.yaml#/components/parameters/cmHandleInPath'
-      - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery'
-      - $ref: 'components.yaml#/components/parameters/optionsParamInQuery'
-      - $ref: 'components.yaml#/components/parameters/topicParamInQuery'
-    responses:
-      200:
-        description: OK
-        content:
-          application/json:
-            schema:
-              type: object
-            examples:
-              dataSampleResponse:
-                $ref: 'components.yaml#/components/examples/dataSampleResponse'
-      400:
-        $ref: 'components.yaml#/components/responses/BadRequest'
-      401:
-        $ref: 'components.yaml#/components/responses/Unauthorized'
-      403:
-        $ref: 'components.yaml#/components/responses/Forbidden'
-      500:
-        $ref: 'components.yaml#/components/responses/InternalServerError'
-      502:
-        $ref: 'components.yaml#/components/responses/BadGateway'
 
-resourceDataForPassthroughRunning:
+resourceDataForCmHandle:
   get:
     tags:
       - network-cm-proxy
-    summary: Get resource data from pass-through running for cm handle
-    description: Get resource data from pass-through running for given cm handle
-    operationId: getResourceDataRunningForCmHandle
+    summary: Get resource data for cm handle
+    description: Get resource data for given cm handle
+    operationId: getResourceDataForCmHandle
     parameters:
+      - $ref: 'components.yaml#/components/parameters/datastoreName'
       - $ref: 'components.yaml#/components/parameters/cmHandleInPath'
       - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery'
       - $ref: 'components.yaml#/components/parameters/optionsParamInQuery'
       - $ref: 'components.yaml#/components/parameters/topicParamInQuery'
+      - $ref: 'components.yaml#/components/parameters/includeDescendantsOptionInQuery'
     responses:
       200:
         description: OK
@@ -82,6 +52,7 @@ resourceDataForPassthroughRunning:
         $ref: 'components.yaml#/components/responses/InternalServerError'
       502:
         $ref: 'components.yaml#/components/responses/BadGateway'
+
   post:
     tags:
       - network-cm-proxy
@@ -89,6 +60,7 @@ resourceDataForPassthroughRunning:
     description: create resource data from pass-through running for given cm handle
     operationId: createResourceDataRunningForCmHandle
     parameters:
+      - $ref: 'components.yaml#/components/parameters/datastoreName'
       - $ref: 'components.yaml#/components/parameters/cmHandleInPath'
       - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery'
       - $ref: 'components.yaml#/components/parameters/contentParamInHeader'
@@ -128,6 +100,7 @@ resourceDataForPassthroughRunning:
     description: Update resource data from pass-through running for the given cm handle
     operationId: updateResourceDataRunningForCmHandle
     parameters:
+      - $ref: 'components.yaml#/components/parameters/datastoreName'
       - $ref: 'components.yaml#/components/parameters/cmHandleInPath'
       - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery'
       - $ref: 'components.yaml#/components/parameters/contentParamInHeader'
@@ -167,6 +140,7 @@ resourceDataForPassthroughRunning:
     description: Patch resource data from pass-through running for the given cm handle
     operationId: patchResourceDataRunningForCmHandle
     parameters:
+      - $ref: 'components.yaml#/components/parameters/datastoreName'
       - $ref: 'components.yaml#/components/parameters/cmHandleInPath'
       - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery'
       - $ref: 'components.yaml#/components/parameters/contentParamInHeader'
@@ -200,6 +174,7 @@ resourceDataForPassthroughRunning:
     description: Delete resource data from pass-through running for a given cm handle
     operationId: deleteResourceDataRunningForCmHandle
     parameters:
+      - $ref: 'components.yaml#/components/parameters/datastoreName'
       - $ref: 'components.yaml#/components/parameters/cmHandleInPath'
       - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery'
       - $ref: 'components.yaml#/components/parameters/contentParamInHeader'
index 8e02066..4c546be 100755 (executable)
@@ -26,11 +26,8 @@ info:
 servers:
   - url: /ncmp
 paths:
-  /v1/ch/{cm-handle}/data/ds/ncmp-datastore:passthrough-operational:
-    $ref: 'ncmp.yml#/getResourceDataForPassthroughOperational'
-
-  /v1/ch/{cm-handle}/data/ds/ncmp-datastore:passthrough-running:
-    $ref: 'ncmp.yml#/resourceDataForPassthroughRunning'
+  /v1/ch/{cm-handle}/data/ds/{ncmp-datastore-name}:
+    $ref: 'ncmp.yml#/resourceDataForCmHandle'
 
   /v1/ch/{cm-handle}/modules:
     $ref: 'ncmp.yml#/fetchModuleReferencesByCmHandle'
index 6a700c3..b3021d2 100644 (file)
                             <goal>copy-resources</goal>
                         </goals>
                         <configuration>
-                            <outputDirectory>${project.basedir}/target/classes/static/api-docs/cps-ncmp</outputDirectory>
+                            <outputDirectory>${project.basedir}/target/classes/static/api-docs/cps-ncmp
+                            </outputDirectory>
                             <resources>
                                 <resource>
                                     <directory>${project.basedir}/target/generated-sources/swagger/</directory>
index d2ed393..2f6668a 100755 (executable)
@@ -29,21 +29,19 @@ import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum
 import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.UPDATE;
 
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
-import java.util.UUID;
 import java.util.stream.Collectors;
-import javax.validation.Valid;
-import javax.validation.constraints.NotNull;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.ncmp.api.NetworkCmProxyDataService;
-import org.onap.cps.ncmp.api.impl.exception.InvalidTopicException;
 import org.onap.cps.ncmp.api.inventory.CompositeState;
 import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters;
 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
 import org.onap.cps.ncmp.rest.api.NetworkCmProxyApi;
-import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor;
+import org.onap.cps.ncmp.rest.controller.handlers.DatastoreType;
+import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreResourceRequestHandler;
+import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreResourceRequestHandlerFactory;
+import org.onap.cps.ncmp.rest.exceptions.InvalidDatastoreException;
 import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper;
 import org.onap.cps.ncmp.rest.model.CmHandlePublicProperties;
 import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters;
@@ -53,9 +51,7 @@ import org.onap.cps.ncmp.rest.model.RestOutputCmHandle;
 import org.onap.cps.ncmp.rest.model.RestOutputCmHandleCompositeState;
 import org.onap.cps.ncmp.rest.model.RestOutputCmHandlePublicProperties;
 import org.onap.cps.ncmp.rest.util.DeprecationHelper;
-import org.onap.cps.utils.CpsValidator;
 import org.onap.cps.utils.JsonObjectMapper;
-import org.springframework.beans.factory.annotation.Value;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -68,94 +64,65 @@ import org.springframework.web.bind.annotation.RestController;
 public class NetworkCmProxyController implements NetworkCmProxyApi {
 
     private static final String NO_BODY = null;
-    private static final String NO_REQUEST_ID = null;
-    private static final String NO_TOPIC = null;
     private final NetworkCmProxyDataService networkCmProxyDataService;
     private final JsonObjectMapper jsonObjectMapper;
-
     private final DeprecationHelper deprecationHelper;
     private final NcmpRestInputMapper ncmpRestInputMapper;
     private final CmHandleStateMapper cmHandleStateMapper;
-    private final CpsNcmpTaskExecutor cpsNcmpTaskExecutor;
-    @Value("${notification.async.executor.time-out-value-in-ms:2000}")
-    private int timeOutInMilliSeconds;
-    @Value("${notification.enabled:true}")
-    private boolean asyncEnabled;
+    private final NcmpDatastoreResourceRequestHandlerFactory ncmpDatastoreResourceRequestHandlerFactory;
 
     /**
-     * Get resource data from operational datastore.
+     * Get resource data from datastore.
      *
-     * @param cmHandle cm handle identifier
-     * @param resourceIdentifier resource identifier
+     * @param datastoreName       name of the datastore
+     * @param cmHandle            cm handle identifier
+     * @param resourceIdentifier  resource identifier
      * @param optionsParamInQuery options query parameter
-     * @param topicParamInQuery topic query parameter
+     * @param topicParamInQuery   topic query parameter
+     * @param includeDescendants  whether include descendants
      * @return {@code ResponseEntity} response from dmi plugin
      */
+
     @Override
-    public ResponseEntity<Object> getResourceDataOperationalForCmHandle(final String cmHandle,
-                                                                        final @NotNull @Valid String resourceIdentifier,
-                                                                        final @Valid String optionsParamInQuery,
-                                                                        final @Valid String topicParamInQuery) {
-        if (asyncEnabled && isValidTopic(topicParamInQuery)) {
-            final String requestId = UUID.randomUUID().toString();
-            log.info("Received Async passthrough-operational request with id {}", requestId);
-            cpsNcmpTaskExecutor.executeTask(() ->
-                    networkCmProxyDataService.getResourceDataOperationalForCmHandle(
-                        cmHandle, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId
-                    ), timeOutInMilliSeconds
-            );
-            return ResponseEntity.ok(Map.of("requestId", requestId));
-        } else {
-            log.warn("Asynchronous messaging is currently disabled for passthrough-operational."
-                + " Will use synchronous operation.");
-        }
+    public ResponseEntity<Object> getResourceDataForCmHandle(final String datastoreName,
+                                                             final String cmHandle,
+                                                             final String resourceIdentifier,
+                                                             final String optionsParamInQuery,
+                                                             final String topicParamInQuery,
+                                                             final Boolean includeDescendants) {
 
-        final Object responseObject = networkCmProxyDataService.getResourceDataOperationalForCmHandle(
-            cmHandle, resourceIdentifier, optionsParamInQuery, NO_TOPIC, NO_REQUEST_ID);
+        final NcmpDatastoreResourceRequestHandler ncmpDatastoreResourceRequestHandler =
+                ncmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceRequestHandler(
+                        DatastoreType.fromDatastoreName(datastoreName));
 
-        return ResponseEntity.ok(responseObject);
+        return ncmpDatastoreResourceRequestHandler.getResourceData(cmHandle, resourceIdentifier,
+                optionsParamInQuery, topicParamInQuery, includeDescendants);
     }
 
     /**
-     * Get resource data from pass-through running datastore.
+     * Patch resource data from passthrough-running.
      *
-     * @param cmHandle cm handle identifier
      * @param resourceIdentifier resource identifier
-     * @param optionsParamInQuery options query parameter
-     * @param topicParamInQuery topic query parameter
+     * @param datastoreName      name of the datastore
+     * @param cmHandle           cm handle identifier
+     * @param requestBody        the request body
+     * @param contentType        content type of body
      * @return {@code ResponseEntity} response from dmi plugin
      */
-    @Override
-    public ResponseEntity<Object> getResourceDataRunningForCmHandle(final String cmHandle,
-                                                                    final @NotNull @Valid String resourceIdentifier,
-                                                                    final @Valid String optionsParamInQuery,
-                                                                    final @Valid String topicParamInQuery) {
-        if (asyncEnabled && isValidTopic(topicParamInQuery)) {
-            final String requestId = UUID.randomUUID().toString();
-            log.info("Received Async passthrough-running request with id {}", requestId);
-            cpsNcmpTaskExecutor.executeTask(() ->
-                networkCmProxyDataService.getResourceDataPassThroughRunningForCmHandle(
-                    cmHandle, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId
-                ), timeOutInMilliSeconds
-            );
-            return ResponseEntity.ok(Map.of("requestId", requestId));
-        } else {
-            log.warn("Asynchronous messaging is currently disabled for passthrough-running."
-                + " Will use synchronous operation.");
-        }
-
-        final Object responseObject = networkCmProxyDataService.getResourceDataPassThroughRunningForCmHandle(
-            cmHandle, resourceIdentifier, optionsParamInQuery, NO_TOPIC, NO_REQUEST_ID);
-
-        return ResponseEntity.ok(responseObject);
-    }
 
     @Override
     public ResponseEntity<Object> patchResourceDataRunningForCmHandle(final String resourceIdentifier,
-        final String cmHandle,
-        final Object requestBody, final String contentType) {
-        final Object responseObject = networkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle(cmHandle,
-            resourceIdentifier, PATCH, jsonObjectMapper.asJsonString(requestBody), contentType);
+                                                                      final String datastoreName,
+                                                                      final String cmHandle,
+                                                                      final Object requestBody,
+                                                                      final String contentType) {
+
+        acceptPassthroughRunningOnly(datastoreName);
+
+        final Object responseObject = networkCmProxyDataService
+                .writeResourceDataPassThroughRunningForCmHandle(
+                        cmHandle, resourceIdentifier, PATCH,
+                        jsonObjectMapper.asJsonString(requestBody), contentType);
         return ResponseEntity.ok(responseObject);
     }
 
@@ -163,14 +130,21 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
      * Create resource data in datastore pass-through running for given cm-handle.
      *
      * @param resourceIdentifier resource identifier
-     * @param cmHandle cm handle identifier
-     * @param requestBody the request body
-     * @param contentType content type of body
+     * @param datastoreName      name of the datastore
+     * @param cmHandle           cm handle identifier
+     * @param requestBody        the request body
+     * @param contentType        content type of body
      * @return {@code ResponseEntity} response from dmi plugin
      */
     @Override
     public ResponseEntity<Void> createResourceDataRunningForCmHandle(final String resourceIdentifier,
-        final String cmHandle, final Object requestBody, final String contentType) {
+                                                                     final String datastoreName,
+                                                                     final String cmHandle,
+                                                                     final Object requestBody,
+                                                                     final String contentType) {
+
+        acceptPassthroughRunningOnly(datastoreName);
+
         networkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle(cmHandle,
                 resourceIdentifier, CREATE, jsonObjectMapper.asJsonString(requestBody), contentType);
         return new ResponseEntity<>(HttpStatus.CREATED);
@@ -180,36 +154,45 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
      * Update resource data in datastore pass-through running for given cm-handle.
      *
      * @param resourceIdentifier resource identifier
-     * @param cmHandle cm handle identifier
-     * @param requestBody the request body
-     * @param contentType content type of the body
+     * @param datastoreName      name of the datastore
+     * @param cmHandle           cm handle identifier
+     * @param requestBody        the request body
+     * @param contentType        content type of the body
      * @return response entity
      */
+
     @Override
     public ResponseEntity<Object> updateResourceDataRunningForCmHandle(final String resourceIdentifier,
+                                                                       final String datastoreName,
                                                                        final String cmHandle,
                                                                        final Object requestBody,
                                                                        final String contentType) {
+        acceptPassthroughRunningOnly(datastoreName);
+
         networkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle(cmHandle,
                 resourceIdentifier, UPDATE, jsonObjectMapper.asJsonString(requestBody), contentType);
         return new ResponseEntity<>(HttpStatus.OK);
     }
 
-
     /**
-     *  Delete resource data in datastore pass-through running for a given cm-handle.
+     * Delete resource data in datastore pass-through running for a given cm-handle.
      *
+     * @param datastoreName      name of the datastore
+     * @param cmHandle           cm handle identifier
      * @param resourceIdentifier resource identifier
-     * @param cmHandle cm handle identifier
-     * @param contentType content type of the body
+     * @param contentType        content type of the body
      * @return response entity no content if request is successful
      */
     @Override
-    public ResponseEntity<Void> deleteResourceDataRunningForCmHandle(final String cmHandle,
+    public ResponseEntity<Void> deleteResourceDataRunningForCmHandle(final String datastoreName,
+                                                                     final String cmHandle,
                                                                      final String resourceIdentifier,
                                                                      final String contentType) {
+
+        acceptPassthroughRunningOnly(datastoreName);
+
         networkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle(cmHandle,
-            resourceIdentifier, DELETE, NO_BODY, contentType);
+                resourceIdentifier, DELETE, NO_BODY, contentType);
         return new ResponseEntity<>(HttpStatus.NO_CONTENT);
     }
 
@@ -240,7 +223,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
      */
     @Override
     public ResponseEntity<List<String>> searchCmHandleIds(
-        final CmHandleQueryParameters cmHandleQueryParameters) {
+            final CmHandleQueryParameters cmHandleQueryParameters) {
         final CmHandleQueryApiParameters cmHandleQueryApiParameters =
                 jsonObjectMapper.convertToValueType(cmHandleQueryParameters, CmHandleQueryApiParameters.class);
         final Set<String> cmHandleIds = networkCmProxyDataService.executeCmHandleIdSearch(cmHandleQueryApiParameters);
@@ -249,6 +232,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
 
     /**
      * Search for Cm Handle and Properties by Name.
+     *
      * @param cmHandleId cm-handle identifier
      * @return cm handle and its properties
      */
@@ -261,33 +245,35 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
 
     /**
      * Get Cm Handle Properties by Cm Handle Id.
+     *
      * @param cmHandleId cm-handle identifier
      * @return cm handle properties
      */
     @Override
     public ResponseEntity<RestOutputCmHandlePublicProperties> getCmHandlePublicPropertiesByCmHandleId(
-        final String cmHandleId) {
+            final String cmHandleId) {
         final CmHandlePublicProperties cmHandlePublicProperties = new CmHandlePublicProperties();
         cmHandlePublicProperties.add(networkCmProxyDataService.getCmHandlePublicProperties(cmHandleId));
         final RestOutputCmHandlePublicProperties restOutputCmHandlePublicProperties =
-            new RestOutputCmHandlePublicProperties();
+                new RestOutputCmHandlePublicProperties();
         restOutputCmHandlePublicProperties.setPublicCmHandleProperties(cmHandlePublicProperties);
         return ResponseEntity.ok(restOutputCmHandlePublicProperties);
     }
 
     /**
      * Get Cm Handle State by Cm Handle Id.
+     *
      * @param cmHandleId cm-handle identifier
      * @return cm handle state
      */
     @Override
     public ResponseEntity<RestOutputCmHandleCompositeState> getCmHandleStateByCmHandleId(
-        final String cmHandleId) {
+            final String cmHandleId) {
         final CompositeState cmHandleState = networkCmProxyDataService.getCmHandleCompositeState(cmHandleId);
         final RestOutputCmHandleCompositeState restOutputCmHandleCompositeState =
-            new RestOutputCmHandleCompositeState();
+                new RestOutputCmHandleCompositeState();
         restOutputCmHandleCompositeState.setState(
-            cmHandleStateMapper.toCmHandleCompositeStateExternalLockReason(cmHandleState));
+                cmHandleStateMapper.toCmHandleCompositeStateExternalLockReason(cmHandleState));
         return ResponseEntity.ok(restOutputCmHandleCompositeState);
     }
 
@@ -314,26 +300,27 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
      */
     public ResponseEntity<List<RestModuleReference>> getModuleReferencesByCmHandle(final String cmHandle) {
         final List<RestModuleReference> restModuleReferences =
-            networkCmProxyDataService.getYangResourcesModuleReferences(cmHandle).stream()
-            .map(ncmpRestInputMapper::toRestModuleReference)
-                .collect(Collectors.toList());
+                networkCmProxyDataService.getYangResourcesModuleReferences(cmHandle).stream()
+                        .map(ncmpRestInputMapper::toRestModuleReference)
+                        .collect(Collectors.toList());
         return new ResponseEntity<>(restModuleReferences, HttpStatus.OK);
     }
 
     /**
      * Set the data sync enabled flag, along with the data sync state for the specified cm handle.
      *
-     * @param cmHandleId cm handle id
+     * @param cmHandleId          cm handle id
      * @param dataSyncEnabledFlag data sync enabled flag
      * @return response entity ok if request is successful
      */
     @Override
     public ResponseEntity<Object> setDataSyncEnabledFlagForCmHandle(final String cmHandleId,
-                                                                final Boolean dataSyncEnabledFlag) {
+                                                                    final Boolean dataSyncEnabledFlag) {
         networkCmProxyDataService.setDataSyncEnabled(cmHandleId, dataSyncEnabledFlag);
         return new ResponseEntity<>(HttpStatus.OK);
     }
 
+
     private RestOutputCmHandle toRestOutputCmHandle(final NcmpServiceCmHandle ncmpServiceCmHandle) {
         final RestOutputCmHandle restOutputCmHandle = new RestOutputCmHandle();
         final CmHandlePublicProperties cmHandlePublicProperties = new CmHandlePublicProperties();
@@ -345,15 +332,12 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
         return restOutputCmHandle;
     }
 
-    private static boolean isValidTopic(final String topicName) {
-        if (topicName == null) {
-            return false;
-        }
-        if (CpsValidator.validateTopicName(topicName)) {
-            return true;
+    private void acceptPassthroughRunningOnly(final String datastoreName) {
+        final DatastoreType datastoreType = DatastoreType.fromDatastoreName(datastoreName);
+
+        if (DatastoreType.PASSTHROUGH_RUNNING != datastoreType) {
+            throw new InvalidDatastoreException(datastoreName + " is not supported");
         }
-        throw new InvalidTopicException("Topic name " + topicName + " is invalid", "invalid topic");
     }
-
 }
 
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/DatastoreType.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/DatastoreType.java
new file mode 100644 (file)
index 0000000..e8ab997
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ *  ============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.rest.controller.handlers;
+
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import lombok.Getter;
+import org.onap.cps.ncmp.rest.exceptions.InvalidDatastoreException;
+
+@Getter
+public enum DatastoreType {
+
+    OPERATIONAL("ncmp-datastore:operational"),
+    PASSTHROUGH_RUNNING("ncmp-datastore:passthrough-running"),
+    PASSTHROUGH_OPERATIONAL("ncmp-datastore:passthrough-operational");
+
+    DatastoreType(final String datastoreName) {
+        this.datastoreName = datastoreName;
+    }
+
+    private final String datastoreName;
+    private static final Map<String, DatastoreType> datastoreNameToDatastoreType = new HashMap<>();
+
+    static {
+        Arrays.stream(DatastoreType.values()).forEach(
+                type -> datastoreNameToDatastoreType.put(type.getDatastoreName(), type));
+    }
+
+    /**
+     * From datastore name get datastore type.
+     *
+     * @param datastoreName the datastore name
+     * @return the datastore type
+     */
+    public static DatastoreType fromDatastoreName(final String datastoreName) {
+
+        final DatastoreType datastoreType = datastoreNameToDatastoreType.get(datastoreName);
+
+        if (null == datastoreType) {
+            throw new InvalidDatastoreException(datastoreName + " is an invalid datastore name");
+        }
+
+        return datastoreType;
+    }
+
+}
+
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreOperationalResourceRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreOperationalResourceRequestHandler.java
new file mode 100644 (file)
index 0000000..6ed9b8c
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ *  ============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.rest.controller.handlers;
+
+import java.util.function.Supplier;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.ncmp.api.NetworkCmProxyDataService;
+import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor;
+import org.onap.cps.spi.FetchDescendantsOption;
+
+@Slf4j
+public class NcmpDatastoreOperationalResourceRequestHandler extends NcmpDatastoreResourceRequestHandler {
+
+    public NcmpDatastoreOperationalResourceRequestHandler(final NetworkCmProxyDataService networkCmProxyDataService,
+                                                          final CpsNcmpTaskExecutor cpsNcmpTaskExecutor,
+                                                          final int timeOutInMilliSeconds,
+                                                          final boolean notificationFeatureEnabled) {
+        super(networkCmProxyDataService, cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled);
+    }
+
+    @Override
+    public Supplier<Object> getTask(final String cmHandle,
+                                    final String resourceIdentifier,
+                                    final String optionsParamInQuery,
+                                    final String topicParamInQuery,
+                                    final String requestId,
+                                    final Boolean includeDescendant) {
+
+        final FetchDescendantsOption fetchDescendantsOption =
+                Boolean.TRUE.equals(includeDescendant) ? FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
+                        : FetchDescendantsOption.OMIT_DESCENDANTS;
+
+        return () -> networkCmProxyDataService.getResourceDataOperational(cmHandle, resourceIdentifier,
+                fetchDescendantsOption);
+    }
+
+}
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughOperationalResourceRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughOperationalResourceRequestHandler.java
new file mode 100644 (file)
index 0000000..196e5bd
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ *  ============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.rest.controller.handlers;
+
+import java.util.function.Supplier;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.ncmp.api.NetworkCmProxyDataService;
+import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor;
+
+@Slf4j
+public class NcmpDatastorePassthroughOperationalResourceRequestHandler extends NcmpDatastoreResourceRequestHandler {
+
+    public NcmpDatastorePassthroughOperationalResourceRequestHandler(
+            final NetworkCmProxyDataService networkCmProxyDataService,
+            final CpsNcmpTaskExecutor cpsNcmpTaskExecutor,
+            final int timeOutInMilliSeconds,
+            final boolean notificationFeatureEnabled) {
+        super(networkCmProxyDataService, cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled);
+    }
+
+    @Override
+    public Supplier<Object> getTask(final String cmHandle,
+                                    final String resourceIdentifier,
+                                    final String optionsParamInQuery,
+                                    final String topicParamInQuery,
+                                    final String requestId,
+                                    final Boolean includeDescendant) {
+
+        return () -> networkCmProxyDataService.getResourceDataOperationalForCmHandle(
+                cmHandle, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId);
+    }
+
+}
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughRunningResourceRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastorePassthroughRunningResourceRequestHandler.java
new file mode 100644 (file)
index 0000000..5bf16b7
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ *  ============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.rest.controller.handlers;
+
+import java.util.function.Supplier;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.ncmp.api.NetworkCmProxyDataService;
+import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor;
+
+@Slf4j
+public class NcmpDatastorePassthroughRunningResourceRequestHandler extends NcmpDatastoreResourceRequestHandler {
+
+    public NcmpDatastorePassthroughRunningResourceRequestHandler(
+            final NetworkCmProxyDataService networkCmProxyDataService,
+            final CpsNcmpTaskExecutor cpsNcmpTaskExecutor,
+            final int timeOutInMilliSeconds,
+            final boolean notificationFeatureEnabled) {
+        super(networkCmProxyDataService, cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled);
+    }
+
+    @Override
+    public Supplier<Object> getTask(final String cmHandle,
+                                    final String resourceIdentifier,
+                                    final String optionsParamInQuery,
+                                    final String topicParamInQuery,
+                                    final String requestId,
+                                    final Boolean includeDescendant) {
+
+        return () -> networkCmProxyDataService.getResourceDataPassThroughRunningForCmHandle(
+                cmHandle, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId);
+    }
+}
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandler.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandler.java
new file mode 100644 (file)
index 0000000..a6d313b
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ *  ============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.rest.controller.handlers;
+
+import java.util.Map;
+import java.util.UUID;
+import java.util.function.Supplier;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.ncmp.api.NetworkCmProxyDataService;
+import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor;
+import org.onap.cps.ncmp.rest.util.TopicValidator;
+import org.springframework.http.ResponseEntity;
+
+@RequiredArgsConstructor
+@Slf4j
+public abstract class NcmpDatastoreResourceRequestHandler {
+
+    private static final String NO_REQUEST_ID = null;
+    private static final String NO_TOPIC = null;
+
+    protected final NetworkCmProxyDataService networkCmProxyDataService;
+    protected final CpsNcmpTaskExecutor cpsNcmpTaskExecutor;
+    protected final int timeOutInMilliSeconds;
+    protected final boolean notificationFeatureEnabled;
+
+    protected abstract Supplier<Object> getTask(final String cmHandle,
+                                                final String resourceIdentifier,
+                                                final String optionsParamInQuery,
+                                                final String topicParamInQuery,
+                                                final String requestId,
+                                                final Boolean includeDescendant);
+
+
+    /**
+     * Get resource data from datastore.
+     *
+     * @param cmHandleId          the cm handle
+     * @param resourceIdentifier  the resource identifier
+     * @param optionsParamInQuery the options param in query
+     * @param topicParamInQuery   the topic param in query
+     * @param includeDescendants  whether include descendants
+     * @return the response entity
+     */
+    public ResponseEntity<Object> getResourceData(final String cmHandleId,
+                                                  final String resourceIdentifier,
+                                                  final String optionsParamInQuery,
+                                                  final String topicParamInQuery,
+                                                  final Boolean includeDescendants) {
+
+        final String requestId = UUID.randomUUID().toString();
+        final boolean asyncResponseRequested = topicParamInQuery != null;
+
+        if (asyncResponseRequested && notificationFeatureEnabled) {
+            TopicValidator.validateTopicName(topicParamInQuery);
+            log.debug("Received Async request with id {}", requestId);
+            cpsNcmpTaskExecutor.executeTask(
+                    getTask(cmHandleId, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId,
+                            includeDescendants), timeOutInMilliSeconds);
+
+            return ResponseEntity.ok(Map.of("requestId", requestId));
+        }
+
+        if (asyncResponseRequested) {
+            log.warn("Asynchronous messaging is currently disabled, will use synchronous operation.");
+        }
+        
+        final Object responseObject =
+                getTask(cmHandleId, resourceIdentifier, optionsParamInQuery, NO_TOPIC, NO_REQUEST_ID,
+                        includeDescendants).get();
+
+        return ResponseEntity.ok(responseObject);
+    }
+}
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandlerFactory.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandlerFactory.java
new file mode 100644 (file)
index 0000000..35bd578
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ *  ============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.rest.controller.handlers;
+
+import lombok.RequiredArgsConstructor;
+import org.onap.cps.ncmp.api.NetworkCmProxyDataService;
+import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class NcmpDatastoreResourceRequestHandlerFactory {
+
+    private final NetworkCmProxyDataService networkCmProxyDataService;
+    private final CpsNcmpTaskExecutor cpsNcmpTaskExecutor;
+
+    @Value("${notification.async.executor.time-out-value-in-ms:2000}")
+    private int timeOutInMilliSeconds;
+    @Value("${notification.enabled:true}")
+    private boolean notificationFeatureEnabled;
+
+    /**
+     * Gets ncmp datastore handler.
+     *
+     * @param datastoreType the datastore type
+     * @return the ncmp datastore handler
+     */
+    public NcmpDatastoreResourceRequestHandler getNcmpDatastoreResourceRequestHandler(
+            final DatastoreType datastoreType) {
+
+        switch (datastoreType) {
+            case OPERATIONAL:
+                return new NcmpDatastoreOperationalResourceRequestHandler(networkCmProxyDataService,
+                        cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled);
+            case PASSTHROUGH_RUNNING:
+                return new NcmpDatastorePassthroughRunningResourceRequestHandler(networkCmProxyDataService,
+                        cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled);
+            case PASSTHROUGH_OPERATIONAL:
+                return new NcmpDatastorePassthroughOperationalResourceRequestHandler(networkCmProxyDataService,
+                        cpsNcmpTaskExecutor, timeOutInMilliSeconds, notificationFeatureEnabled);
+            default:
+                return null;
+        }
+    }
+}
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/InvalidDatastoreException.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/exceptions/InvalidDatastoreException.java
new file mode 100644 (file)
index 0000000..ff13a93
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ *  ============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.rest.exceptions;
+
+public class InvalidDatastoreException extends RuntimeException {
+    /**
+     * Instantiates a new Invalid datastore exception.
+     *
+     * @param message the message
+     */
+    public InvalidDatastoreException(final String message) {
+        super(message);
+    }
+}
index c723733..58a60d2 100755 (executable)
@@ -25,7 +25,6 @@ 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.HttpClientRequestException;
-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;
@@ -33,6 +32,8 @@ import org.onap.cps.ncmp.rest.controller.NetworkCmProxyInventoryController;
 import org.onap.cps.ncmp.rest.model.DmiErrorMessage;
 import org.onap.cps.ncmp.rest.model.DmiErrorMessageDmiresponse;
 import org.onap.cps.ncmp.rest.model.ErrorMessage;
+import org.onap.cps.spi.exceptions.AlreadyDefinedException;
+import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch;
 import org.onap.cps.spi.exceptions.CpsException;
 import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
 import org.onap.cps.spi.exceptions.DataValidationException;
@@ -60,7 +61,7 @@ public class NetworkCmProxyRestExceptionHandler {
      */
     @ExceptionHandler
     public static ResponseEntity<Object> handleInternalServerErrorExceptions(
-        final Exception exception) {
+            final Exception exception) {
         return buildErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, exception);
     }
 
@@ -75,12 +76,17 @@ public class NetworkCmProxyRestExceptionHandler {
         return wrapDmiErrorResponse(HttpStatus.BAD_GATEWAY, httpClientRequestException);
     }
 
-    @ExceptionHandler({DmiRequestException.class, DataValidationException.class, HttpMessageNotReadableException.class,
-            InvalidTopicException.class})
+    @ExceptionHandler({DmiRequestException.class, DataValidationException.class,
+            HttpMessageNotReadableException.class, InvalidTopicException.class, InvalidDatastoreException.class})
     public static ResponseEntity<Object> handleDmiRequestExceptions(final Exception exception) {
         return buildErrorResponse(HttpStatus.BAD_REQUEST, exception);
     }
 
+    @ExceptionHandler({AlreadyDefinedException.class, AlreadyDefinedExceptionBatch.class })
+    public static ResponseEntity<Object> handleAlreadyDefinedExceptions(final Exception exception) {
+        return buildErrorResponse(HttpStatus.CONFLICT, exception);
+    }
+
     @ExceptionHandler({DataNodeNotFoundException.class})
     public static ResponseEntity<Object> handleNotFoundExceptions(final CpsException exception) {
         return buildErrorResponse(HttpStatus.NOT_FOUND, exception);
@@ -105,7 +111,8 @@ public class NetworkCmProxyRestExceptionHandler {
         return new ResponseEntity<>(errorMessage, status);
     }
 
-    private static ResponseEntity<Object> wrapDmiErrorResponse(final HttpStatus httpStatus,
+    private static ResponseEntity<Object> wrapDmiErrorResponse(
+            final HttpStatus httpStatus,
             final HttpClientRequestException httpClientRequestException) {
         final var dmiErrorMessage = new DmiErrorMessage();
         final var dmiErrorResponse = new DmiErrorMessageDmiresponse();
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/TopicValidator.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/util/TopicValidator.java
new file mode 100644 (file)
index 0000000..313e7bc
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ *  ============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.rest.util;
+
+import java.util.regex.Pattern;
+import org.onap.cps.ncmp.rest.exceptions.InvalidTopicException;
+
+public class TopicValidator {
+
+    private static final Pattern TOPIC_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9]([._-](?![._-])|"
+        + "[a-zA-Z0-9]){0,120}[a-zA-Z0-9]$");
+
+    /**
+     * Validate kafka topic name pattern.
+     *
+     * @param topicName name of the topic to be validated
+     *
+     * @throws InvalidTopicException if the topic is not valid
+     */
+    public static void validateTopicName(final String topicName) {
+        if (!TOPIC_NAME_PATTERN.matcher(topicName).matches()) {
+            throw new InvalidTopicException("Topic name " + topicName + " is invalid", "invalid topic");
+        }
+    }
+
+}
index d568a5a..b6194bc 100644 (file)
 
 package org.onap.cps.ncmp.rest.controller
 
+import com.fasterxml.jackson.databind.ObjectMapper
 import org.mapstruct.factory.Mappers
+import org.onap.cps.TestUtils
+import org.onap.cps.ncmp.api.NetworkCmProxyDataService
 import org.onap.cps.ncmp.api.inventory.CmHandleState
 import org.onap.cps.ncmp.api.inventory.CompositeState
-import org.onap.cps.ncmp.api.inventory.LockReasonCategory
 import org.onap.cps.ncmp.api.inventory.DataStoreSyncState
+import org.onap.cps.ncmp.api.inventory.LockReasonCategory
 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
-import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper
+import org.onap.cps.ncmp.rest.controller.handlers.DatastoreType
+import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreOperationalResourceRequestHandler
+import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastorePassthroughOperationalResourceRequestHandler
+import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastorePassthroughRunningResourceRequestHandler
+import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreResourceRequestHandlerFactory
 import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor
+import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper
 import org.onap.cps.ncmp.rest.util.DeprecationHelper
+import org.onap.cps.spi.FetchDescendantsOption
 import org.onap.cps.spi.model.ModuleDefinition
+import org.onap.cps.spi.model.ModuleReference
+import org.onap.cps.utils.JsonObjectMapper
+import org.spockframework.spring.SpringBean
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
+import org.springframework.http.HttpStatus
+import org.springframework.http.MediaType
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder
 import spock.lang.Shared
+import spock.lang.Specification
 
 import java.time.OffsetDateTime
 import java.time.ZoneOffset
 import java.time.format.DateTimeFormatter
 
-import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.PATCH
 import static org.onap.cps.ncmp.api.inventory.CompositeState.DataStores
 import static org.onap.cps.ncmp.api.inventory.CompositeState.Operational
-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.patch
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
 import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.CREATE
 import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.UPDATE
+import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.PATCH
 import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum.DELETE
-
-import com.fasterxml.jackson.databind.ObjectMapper
-import org.onap.cps.TestUtils
-import org.onap.cps.spi.model.ModuleReference
-import org.onap.cps.utils.JsonObjectMapper
-import org.onap.cps.ncmp.api.NetworkCmProxyDataService
-import org.spockframework.spring.SpringBean
-import org.springframework.beans.factory.annotation.Autowired
-import org.springframework.beans.factory.annotation.Value
-import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
-import org.springframework.http.HttpStatus
-import org.springframework.http.MediaType
-import org.springframework.test.web.servlet.MockMvc
-import spock.lang.Specification
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
 
 @WebMvcTest(NetworkCmProxyController)
 class NetworkCmProxyControllerSpec extends Specification {
 
+    public static final int TIMEOUT_IN_MS = 2000
+    public static final boolean NOTIFICATION_ENABLED = true
+
     @Autowired
     MockMvc mvc
 
@@ -92,6 +101,9 @@ class NetworkCmProxyControllerSpec extends Specification {
     @SpringBean
     DeprecationHelper stubbedDeprecationHelper = Stub()
 
+    @SpringBean
+    NcmpDatastoreResourceRequestHandlerFactory stubbedNcmpDatastoreResourceRequestHandlerFactory = Stub()
+
     @Value('${rest.api.ncmp-base-path}/v1')
     def ncmpBasePathV1
 
@@ -104,21 +116,38 @@ class NetworkCmProxyControllerSpec extends Specification {
     def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
         .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC))
 
+    void setup() {
+        stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceRequestHandler(
+            DatastoreType.OPERATIONAL) >>
+            new NcmpDatastoreOperationalResourceRequestHandler(
+                mockNetworkCmProxyDataService, spiedCpsTaskExecutor, TIMEOUT_IN_MS, NOTIFICATION_ENABLED)
+
+        stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceRequestHandler(
+            DatastoreType.PASSTHROUGH_OPERATIONAL) >>
+            new NcmpDatastorePassthroughOperationalResourceRequestHandler(
+                mockNetworkCmProxyDataService, spiedCpsTaskExecutor, TIMEOUT_IN_MS, NOTIFICATION_ENABLED)
+
+        stubbedNcmpDatastoreResourceRequestHandlerFactory.getNcmpDatastoreResourceRequestHandler(
+            DatastoreType.PASSTHROUGH_RUNNING) >>
+            new NcmpDatastorePassthroughRunningResourceRequestHandler(
+                mockNetworkCmProxyDataService, spiedCpsTaskExecutor, TIMEOUT_IN_MS, NOTIFICATION_ENABLED)
+    }
+
     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)"
+                "?resourceIdentifier=parent/child&options=(a=1,b=2)"
         when: 'get data resource request is performed'
             def response = mvc.perform(
-                    get(getUrl)
-                            .contentType(MediaType.APPLICATION_JSON)
+                get(getUrl)
+                    .contentType(MediaType.APPLICATION_JSON)
             ).andReturn().response
         then: 'the NCMP data service is called with getResourceDataOperationalForCmHandle'
             1 * mockNetworkCmProxyDataService.getResourceDataOperationalForCmHandle('testCmHandle',
-                    'parent/child',
-                    '(a=1,b=2)',
-                    NO_TOPIC,
-                    NO_REQUEST_ID)
+                'parent/child',
+                '(a=1,b=2)',
+                NO_TOPIC,
+                NO_REQUEST_ID)
         and: 'response status is Ok'
             response.status == HttpStatus.OK.value()
     }
@@ -126,22 +155,22 @@ class NetworkCmProxyControllerSpec extends Specification {
     def 'Get Resource Data from #datastoreInUrl with #scenario.'() {
         given: 'resource data url'
             def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastoreInUrl}" +
-                    "?resourceIdentifier=parent/child&options=(a=1,b=2)${topicQueryParam}"
+                "?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)).andReturn().response
+                get(getUrl).contentType(MediaType.APPLICATION_JSON)).andReturn().response
         then: 'task executor is called appropriate number of times'
-            expectedNumberOfExecutorExecutions * spiedCpsTaskExecutor.executeTask(_, 2000)
+            expectedNumberOfExecutorExecutions * spiedCpsTaskExecutor.executeTask(_, TIMEOUT_IN_MS)
         and: 'response status is expected'
             response.status == HttpStatus.OK.value()
         where: 'the following parameters are used'
-            scenario                               | datastoreInUrl            | topicQueryParam        || expectedTopicName | expectedNumberOfExecutorExecutions
-            'url with valid topic'                 | 'passthrough-operational' | '&topic=my-topic-name' || 'my-topic-name'   | 1
-            'no topic in url'                      | 'passthrough-operational' | ''                     || NO_TOPIC          | 0
-            'null topic in url'                    | 'passthrough-operational' | '&topic=null'          || 'null'            | 1
-            'url with valid topic'                 | 'passthrough-running'     | '&topic=my-topic-name' || 'my-topic-name'   | 1
-            'no topic in url'                      | 'passthrough-running'     | ''                     || NO_TOPIC          | 0
-            'null topic in url'                    | 'passthrough-running'     | '&topic=null'          || 'null'            | 1
+            scenario               | datastoreInUrl            | topicQueryParam        || expectedTopicName | expectedNumberOfExecutorExecutions
+            'url with valid topic' | 'passthrough-operational' | '&topic=my-topic-name' || 'my-topic-name'   | 1
+            'no topic in url'      | 'passthrough-operational' | ''                     || NO_TOPIC          | 0
+            'null topic in url'    | 'passthrough-operational' | '&topic=null'          || 'null'            | 1
+            'url with valid topic' | 'passthrough-running'     | '&topic=my-topic-name' || 'my-topic-name'   | 1
+            'no topic in url'      | 'passthrough-running'     | ''                     || NO_TOPIC          | 0
+            'null topic in url'    | 'passthrough-running'     | '&topic=null'          || 'null'            | 1
     }
 
     def 'Fail to get Resource Data from #datastoreInUrl when #scenario.'() {
@@ -168,17 +197,17 @@ class NetworkCmProxyControllerSpec extends Specification {
     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)"
+                "?resourceIdentifier=" + resourceIdentifier + "&options=(a=1,b=2)"
         and: 'ncmp service returns json object'
             mockNetworkCmProxyDataService.getResourceDataPassThroughRunningForCmHandle('testCmHandle',
-                    resourceIdentifier,
-                    '(a=1,b=2)',
-                    NO_TOPIC,
-                    NO_REQUEST_ID) >> '{valid-json}'
+                resourceIdentifier,
+                '(a=1,b=2)',
+                NO_TOPIC,
+                NO_REQUEST_ID) >> '{valid-json}'
         when: 'get data resource request is performed'
             def response = mvc.perform(
-                    get(getUrl)
-                            .contentType(MediaType.APPLICATION_JSON)
+                get(getUrl)
+                    .contentType(MediaType.APPLICATION_JSON)
             ).andReturn().response
         then: 'response status is Ok'
             response.status == HttpStatus.OK.value()
@@ -194,7 +223,7 @@ class NetworkCmProxyControllerSpec extends Specification {
             '? needs to be encoded as %3F' | 'idWith%3F'
     }
 
-    def 'Update resource data from pass-through running.' () {
+    def 'Update resource data from pass-through running.'() {
         given: 'update resource data url'
             def updateUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
                 "?resourceIdentifier=parent/child"
@@ -210,15 +239,14 @@ class NetworkCmProxyControllerSpec extends Specification {
             response.status == HttpStatus.OK.value()
     }
 
-    def 'Create Resource Data from pass-through running with #scenario.' () {
+    def 'Create Resource Data from pass-through running with #scenario.'() {
         given: 'resource data url'
             def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
-                    "?resourceIdentifier=parent/child"
-            def requestBody = '{"some-key":"some-value"}'
+                "?resourceIdentifier=parent/child"
         when: 'create resource request is performed'
             def response = mvc.perform(
-                    post(url)
-                            .contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
+                post(url)
+                    .contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
             ).andReturn().response
         then: 'ncmp service method to create resource called'
             1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
@@ -227,14 +255,14 @@ class NetworkCmProxyControllerSpec extends Specification {
             response.status == HttpStatus.CREATED.value()
     }
 
-    def 'Get module references for the given dataspace and cm handle.' () {
+    def 'Get module references for the given dataspace and cm handle.'() {
         given: 'get module references url'
             def getUrl = "$ncmpBasePathV1/ch/some-cmhandle/modules"
         when: 'get module resource request is performed'
-            def response =mvc.perform(get(getUrl)).andReturn().response
+            def response = mvc.perform(get(getUrl)).andReturn().response
         then: 'ncmp service method to get yang resource module references is called'
             mockNetworkCmProxyDataService.getYangResourcesModuleReferences('some-cmhandle')
-                    >> [new ModuleReference(moduleName: 'some-name1',revision: '2021-10-03')]
+                >> [new ModuleReference(moduleName: 'some-name1', revision: '2021-10-03')]
         and: 'response contains an array with the module name and revision'
             response.getContentAsString() == '[{"moduleName":"some-name1","revision":"2021-10-03"}]'
         and: 'response returns an OK http code'
@@ -248,15 +276,15 @@ class NetworkCmProxyControllerSpec extends Specification {
         and: 'the service method is invoked with module names and returns two cm handles'
             def cmHandle1 = new NcmpServiceCmHandle()
             cmHandle1.cmHandleId = 'some-cmhandle-id1'
-            cmHandle1.publicProperties = [color:'yellow']
+            cmHandle1.publicProperties = [color: 'yellow']
             def cmHandle2 = new NcmpServiceCmHandle()
             cmHandle2.cmHandleId = 'some-cmhandle-id2'
-            cmHandle2.publicProperties = [color:'green']
+            cmHandle2.publicProperties = [color: 'green']
             mockNetworkCmProxyDataService.executeCmHandleSearch(_) >> [cmHandle1, cmHandle2]
         when: 'the searches api is invoked'
             def response = mvc.perform(post(searchesEndpoint)
-                    .contentType(MediaType.APPLICATION_JSON)
-                    .content(jsonString)).andReturn().response
+                .contentType(MediaType.APPLICATION_JSON)
+                .content(jsonString)).andReturn().response
         then: 'response status returns OK'
             response.status == HttpStatus.OK.value()
         and: 'the expected response content is returned'
@@ -268,14 +296,15 @@ class NetworkCmProxyControllerSpec extends Specification {
             def cmHandleDetailsEndpoint = "$ncmpBasePathV1/ch/some-cm-handle"
         and: 'an existing ncmp service cm handle'
             def cmHandleId = 'some-cm-handle'
-            def dmiProperties = [ prop:'some DMI property' ]
-            def publicProperties = [ "public prop":'some public property' ]
+            def dmiProperties = [prop: 'some DMI property']
+            def publicProperties = ["public prop": 'some public property']
             def compositeState = compositeStateTestObject()
             def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: cmHandleId, dmiProperties: dmiProperties, publicProperties: publicProperties, compositeState: compositeState)
         and: 'the service method is invoked with the cm handle id'
             1 * mockNetworkCmProxyDataService.getNcmpServiceCmHandle('some-cm-handle') >> ncmpServiceCmHandle
         when: 'the cm handle details api is invoked'
-            def response = mvc.perform(get(cmHandleDetailsEndpoint)).andReturn().response
+            def response = mvc.perform(
+                get(cmHandleDetailsEndpoint)).andReturn().response
         then: 'the correct response is returned'
             response.status == HttpStatus.OK.value()
         and: 'the response contains the public properties'
@@ -286,30 +315,34 @@ class NetworkCmProxyControllerSpec extends Specification {
             !response.contentAsString.contains("some DMI property")
     }
 
-    def 'Get Cm Handle public properties by Cm Handle id.' () {
+    def 'Get Cm Handle public properties by Cm Handle id.'() {
         given: 'a cm handle properties endpoint'
             def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle/properties"
         and: 'some cm handle public properties'
-            def publicProperties =  [ 'public prop':'some public property' ]
+            def publicProperties = ['public prop': 'some public property']
         and: 'the service method is invoked with the cm handle id returning the cm handle public properties'
-            1 * mockNetworkCmProxyDataService.getCmHandlePublicProperties('some-cm-handle') >> publicProperties
+            1 * mockNetworkCmProxyDataService
+                .getCmHandlePublicProperties('some-cm-handle') >> publicProperties
         when: 'the cm handle properties api is invoked'
-            def response = mvc.perform(get(cmHandlePropertiesEndpoint)).andReturn().response
+            def response = mvc.perform(
+                get(cmHandlePropertiesEndpoint)).andReturn().response
         then: 'the correct response is returned'
             response.status == HttpStatus.OK.value()
         and: 'the response contains the public properties'
             assertContainsPublicProperties(response)
     }
 
-    def 'Get Cm Handle composite state by Cm Handle id.' () {
+    def 'Get Cm Handle composite state by Cm Handle id.'() {
         given: 'a cm handle state endpoint'
             def cmHandlePropertiesEndpoint = "$ncmpBasePathV1/ch/some-cm-handle/state"
         and: 'some cm handle composite state'
             def compositeState = compositeStateTestObject()
         and: 'the service method is invoked with the cm handle id returning the cm handle composite state'
-            1 * mockNetworkCmProxyDataService.getCmHandleCompositeState('some-cm-handle') >> compositeState
+            1 * mockNetworkCmProxyDataService
+                .getCmHandleCompositeState('some-cm-handle') >> compositeState
         when: 'the cm handle state api is invoked'
-            def response = mvc.perform(get(cmHandlePropertiesEndpoint)).andReturn().response
+            def response = mvc.perform(
+                get(cmHandlePropertiesEndpoint)).andReturn().response
         then: 'the correct response is returned'
             response.status == HttpStatus.OK.value()
         and: 'the response contains the cm handle state'
@@ -323,13 +356,14 @@ class NetworkCmProxyControllerSpec extends Specification {
         and: 'the service method is invoked with module names and returns two cm handles'
             def cmHandel1 = new NcmpServiceCmHandle()
             cmHandel1.cmHandleId = 'some-cmhandle-id1'
-            cmHandel1.publicProperties = [color:'yellow']
+            cmHandel1.publicProperties = [color: 'yellow']
             def cmHandel2 = new NcmpServiceCmHandle()
             cmHandel2.cmHandleId = 'some-cmhandle-id2'
-            cmHandel2.publicProperties = [color:'green']
+            cmHandel2.publicProperties = [color: 'green']
             mockNetworkCmProxyDataService.executeCmHandleSearch(_) >> [cmHandel1, cmHandel2]
         when: 'the searches api is invoked'
-            def response = mvc.perform(post(searchesEndpoint)
+            def response = mvc.perform(
+                post(searchesEndpoint)
                     .contentType(MediaType.APPLICATION_JSON)
                     .content(jsonString)).andReturn().response
         then: 'an empty cm handle identifier is returned'
@@ -342,9 +376,10 @@ class NetworkCmProxyControllerSpec extends Specification {
         and: 'the service method is invoked with module names and returns cm handle ids'
             1 * mockNetworkCmProxyDataService.executeCmHandleIdSearch(_) >> ['some-cmhandle-id1', 'some-cmhandle-id2']
         when: 'the searches api is invoked'
-            def response = mvc.perform(post(searchesEndpoint)
-                .contentType(MediaType.APPLICATION_JSON)
-                .content('{}')).andReturn().response
+            def response = mvc.perform(
+                post(searchesEndpoint)
+                    .contentType(MediaType.APPLICATION_JSON)
+                    .content('{}')).andReturn().response
         then: 'cm handle ids are returned'
             response.contentAsString == '["some-cmhandle-id1","some-cmhandle-id2"]'
     }
@@ -353,37 +388,39 @@ class NetworkCmProxyControllerSpec extends Specification {
         when: 'the searches api is invoked'
             def searchesEndpoint = "$ncmpBasePathV1/ch/id-searches"
             def invalidInputData = '{invalidJson}'
-            def response = mvc.perform(post(searchesEndpoint)
+            def response = mvc.perform(
+                post(searchesEndpoint)
                     .contentType(MediaType.APPLICATION_JSON)
                     .content(invalidInputData)).andReturn().response
         then: 'BAD_REQUEST is returned'
             response.getStatus() == 400
     }
 
-    def 'Patch resource data in pass-through running datastore.' () {
+    def 'Patch resource data in pass-through running datastore.'() {
         given: 'patch resource data url'
             def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
-                    "?resourceIdentifier=parent/child"
+                "?resourceIdentifier=parent/child"
         when: 'patch data resource request is performed'
             def response = mvc.perform(
-                    patch(url)
-                            .contentType(MediaType.APPLICATION_JSON)
-                            .accept(MediaType.APPLICATION_JSON).content(requestBody)
+                patch(url)
+                    .contentType(MediaType.APPLICATION_JSON)
+                    .accept(MediaType.APPLICATION_JSON).content(requestBody)
             ).andReturn().response
         then: 'ncmp service method to update resource is called'
             1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
-                    'parent/child', PATCH, requestBody, 'application/json;charset=UTF-8')
+                'parent/child', PATCH, requestBody, 'application/json;charset=UTF-8')
         and: 'the response status is OK'
             response.status == HttpStatus.OK.value()
     }
 
-    def 'Delete resource data in pass-through running datastore.' () {
+    def 'Delete resource data in pass-through running datastore.'() {
         given: 'delete resource data url'
             def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:passthrough-running" +
-                     "?resourceIdentifier=parent/child"
+                "?resourceIdentifier=parent/child"
         when: 'delete data resource request is performed'
             def response = mvc.perform(
-                delete(url).contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)).andReturn().response
+                delete(url)
+                    .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)).andReturn().response
         then: 'the ncmp service method to delete resource is called (with null as body)'
             1 * mockNetworkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle('testCmHandle',
                 'parent/child', DELETE, null, 'application/json;charset=UTF-8')
@@ -394,12 +431,12 @@ class NetworkCmProxyControllerSpec extends Specification {
     def 'Get resource data from DMI with valid topic i.e. async request for #scenario'() {
         given: 'resource data url'
             def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:${datastoreInUrl}" +
-                    "?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=my-topic-name"
+                "?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=my-topic-name"
         when: 'get data resource request is performed'
             def response = mvc.perform(
-                    get(getUrl)
-                            .contentType(MediaType.APPLICATION_JSON)
-                            .accept(MediaType.APPLICATION_JSON_VALUE)
+                get(getUrl)
+                    .contentType(MediaType.APPLICATION_JSON)
+                    .accept(MediaType.APPLICATION_JSON_VALUE)
             ).andReturn().response
         then: 'async request id is generated'
             assert response.contentAsString.contains("requestId")
@@ -409,32 +446,93 @@ class NetworkCmProxyControllerSpec extends Specification {
             ':passthrough-running'     | 'passthrough-running'
     }
 
-    def 'Get module definitions based on cmHandleId.' () {
+    def 'Get module definitions based on cmHandleId.'() {
         when: 'get module definition request is performed'
-            def response = mvc.perform(get("$ncmpBasePathV1/ch/some-cmhandle/modules/definitions"))
-                    .andReturn().response
+            def response = mvc.perform(
+                get("$ncmpBasePathV1/ch/some-cmhandle/modules/definitions"))
+                .andReturn().response
         then: 'ncmp service method to get module definitions is called'
             mockNetworkCmProxyDataService.getModuleDefinitionsByCmHandleId('some-cmhandle')
-                    >> [new ModuleDefinition('sampleModuleName', '2021-10-03',
-                    'module sampleModuleName{ sample module content }')]
+                >> [new ModuleDefinition('sampleModuleName', '2021-10-03',
+                'module sampleModuleName{ sample module content }')]
         and: 'response contains an array with the module name, revision and content'
             response.getContentAsString() == '[{"moduleName":"sampleModuleName","revision":"2021-10-03","content":"module sampleModuleName{ sample module content }"}]'
         and: 'response returns an OK http code'
             response.status == HttpStatus.OK.value()
     }
 
-    def 'Set the data sync enabled based on the cm handle id and the data sync flag is #scenario' () {
+    def 'Set the data sync enabled based on the cm handle id and the data sync flag is #scenario'() {
         when: 'the set data sync enabled request is invoked'
-            def response = mvc.perform(put("$ncmpBasePathV1/ch/some-cm-handle-id/data-sync?dataSyncEnabled=" + dataSyncEnabledFlag))
-                    .andReturn().response
+            def response = mvc.perform(
+                put("$ncmpBasePathV1/ch/some-cm-handle-id/data-sync?dataSyncEnabled=" + dataSyncEnabledFlag))
+                .andReturn().response
         then: 'method to set data sync enabled is called'
             1 * mockNetworkCmProxyDataService.setDataSyncEnabled('some-cm-handle-id', dataSyncEnabledFlag)
         and: 'the response returns an OK http code'
             response.status == HttpStatus.OK.value()
         where: 'the following parameters are used'
-        scenario     |  dataSyncEnabledFlag
-        'enabled'    |  true
-        'disabled'   |  false
+            scenario   | dataSyncEnabledFlag
+            'enabled'  | true
+            'disabled' | false
+    }
+
+    def 'Get Resource Data from operational with or without descendants'() {
+        given: 'resource data url with descendants #enabled'
+            def getUrl = "$ncmpBasePathV1/ch/testCmHandle/data/ds/ncmp-datastore:operational" +
+                "?resourceIdentifier=parent/child&include-descendants=${enabled}"
+        when: 'get data resource request is performed'
+            def response = mvc.perform(
+                get(getUrl)
+                    .contentType(MediaType.APPLICATION_JSON)
+            ).andReturn().response
+        then: 'the NCMP data service is called with getResourceDataOperational with #descendantsOption'
+            1 * mockNetworkCmProxyDataService.getResourceDataOperational('testCmHandle',
+                'parent/child',
+                descendantsOption)
+        and: 'response status is Ok'
+            response.status == HttpStatus.OK.value()
+        where: 'the following parameters are used'
+            enabled | descendantsOption
+            false   | FetchDescendantsOption.OMIT_DESCENDANTS
+            true    | FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
+    }
+
+    def 'Attempt execute #operation rest operation on resource data with #scenario'() {
+        given: 'resource data url'
+            def url = "$ncmpBasePathV1/ch/testCmHandle/data/ds/${datastoreInUrl}?resourceIdentifier=parent/child"
+        when: 'selected request for data resource is performed on url'
+            def response = mvc.perform(
+                executeRestOperation(operation, url))
+                .andReturn().response
+        then: 'the response status is as expected'
+            assert response.status == HttpStatus.BAD_REQUEST.value()
+        and: 'the response is as expected'
+            assert response.getContentAsString().contains(datastoreInUrl)
+        where: 'the following parameters are used'
+            scenario                | operation | datastoreInUrl
+            'unsupported datastore' | 'POST'    | 'ncmp-datastore:operational'
+            'invalid datastore'     | 'POST'    | 'invalid'
+            'unsupported datastore' | 'PUT'     | 'ncmp-datastore:operational'
+            'invalid datastore'     | 'PUT'     | 'invalid'
+            'unsupported datastore' | 'PATCH'   | 'ncmp-datastore:operational'
+            'invalid datastore'     | 'PATCH'   | 'invalid'
+            'unsupported datastore' | 'DELETE'  | 'ncmp-datastore:operational'
+            'invalid datastore'     | 'DELETE'  | 'invalid'
+    }
+
+    def executeRestOperation(operation, url) {
+        if (operation == 'POST') {
+            return post(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
+        }
+        if (operation == 'PUT') {
+            return put(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
+        }
+        if (operation == 'PATCH') {
+            return patch(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)
+        }
+        if (operation == 'DELETE') {
+            return delete(url).contentType(MediaType.APPLICATION_JSON_VALUE)
+        }
     }
 
     def dataStores() {
@@ -453,7 +551,7 @@ class NetworkCmProxyControllerSpec extends Specification {
     }
 
     def assertContainsAll(response, assertContent) {
-        assertContent.forEach( string -> { assert(response.contentAsString.contains(string)) })
+        assertContent.forEach(string -> { assert (response.contentAsString.contains(string)) })
         return void
     }
 
@@ -476,8 +574,8 @@ class NetworkCmProxyControllerSpec extends Specification {
 
     def assertContainsPublicProperties(response) {
         def expectedContent = [
-            '"publicCmHandleProperties":' ,
-            '"public prop"' ,
+            '"publicCmHandleProperties":',
+            '"public prop"',
             '"some public property"'
         ]
         return assertContainsAll(response, expectedContent)
diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandlerFactorySpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreResourceRequestHandlerFactorySpec.groovy
new file mode 100644 (file)
index 0000000..3f7a8a5
--- /dev/null
@@ -0,0 +1,20 @@
+package org.onap.cps.ncmp.rest.controller.handlers
+
+import spock.lang.Specification
+
+class NcmpDatastoreResourceRequestHandlerFactorySpec extends Specification {
+
+    def objectUnderTest = new NcmpDatastoreResourceRequestHandlerFactory(null, null)
+
+    def 'Creating ncmp datastore request handlers.'() {
+        when: 'a ncmp datastore request handler is created for #datastoreType'
+            def result = objectUnderTest.getNcmpDatastoreResourceRequestHandler(datastoreType)
+        then: 'the result is of the expected class'
+            result.class == expectedClass
+        where: 'the following type of datastore is used'
+            datastoreType                         || expectedClass
+            DatastoreType.OPERATIONAL             || NcmpDatastoreOperationalResourceRequestHandler
+            DatastoreType.PASSTHROUGH_OPERATIONAL || NcmpDatastorePassthroughOperationalResourceRequestHandler
+            DatastoreType.PASSTHROUGH_RUNNING     || NcmpDatastorePassthroughRunningResourceRequestHandler
+    }
+}
\ No newline at end of file
index ce908e7..9d1077f 100644 (file)
@@ -29,9 +29,12 @@ import org.onap.cps.ncmp.api.impl.exception.DmiRequestException
 import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException
 import org.onap.cps.ncmp.api.impl.exception.ServerNcmpException
 import org.onap.cps.ncmp.rest.controller.NcmpRestInputMapper
-import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper
+import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreResourceRequestHandlerFactory
 import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor
+import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper
 import org.onap.cps.ncmp.rest.util.DeprecationHelper
+import org.onap.cps.spi.exceptions.AlreadyDefinedException
+import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch
 import org.onap.cps.spi.exceptions.CpsException
 import org.onap.cps.spi.exceptions.DataNodeNotFoundException
 import org.onap.cps.spi.exceptions.DataValidationException
@@ -48,9 +51,7 @@ import spock.lang.Specification
 
 import static org.onap.cps.ncmp.rest.exceptions.NetworkCmProxyRestExceptionHandlerSpec.ApiType.NCMP
 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.http.HttpStatus.*
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
 
@@ -78,6 +79,9 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification {
     @SpringBean
     DeprecationHelper stubbedDeprecationHelper = Stub()
 
+    @SpringBean
+    NcmpDatastoreResourceRequestHandlerFactory mockedNcmpDatastoreResourceRequestHandlerFactory = Mock()
+
     @Value('${rest.api.ncmp-base-path}')
     def basePathNcmp
 
@@ -104,13 +108,15 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification {
         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(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
+            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
+            'Existing entry'      | new AlreadyDefinedException('name',null)                         || 'name already exists'    | 'Already defined exception' | CONFLICT
+            'Existing entries'    | new AlreadyDefinedExceptionBatch(["x[@id='abc']"])               || 'Check logs for details' | null                        | CONFLICT
     }
 
     def 'Post request with exception returns correct HTTP Status.'() {
@@ -125,7 +131,7 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification {
 
     def 'Failing DMI Request - passthrough scenario'() {
         given: 'failing DMI request'
-            setupTestException(new HttpClientRequestException('Error Message Details NCMP', 'Bad Request from DMI', 400) , NCMP)
+            setupTestException(new HttpClientRequestException('Error Message Details NCMP', 'Bad Request from DMI', 400), NCMP)
         when: 'the DMI request is executed'
             def response = performTestRequest(NCMP)
         then: 'NCMP service responds with 502 Bad Gateway status'
@@ -150,11 +156,11 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification {
         return mvc.perform(post("$dataNodeBaseEndpointNcmpInventory/ch").contentType(MediaType.APPLICATION_JSON).content(jsonData)).andReturn().response
     }
 
-    static void assertTestResponse(response, expectedStatus , expectedErrorMessage , expectedErrorDetails) {
+    static void assertTestResponse(response, expectedStatus, expectedErrorMessage, expectedErrorDetails) {
         assert response.status == expectedStatus.value()
         def content = new JsonSlurper().parseText(response.contentAsString)
         assert content['status'].toString().contains(expectedStatus.toString())
-        assert content['message'].toString().contains(expectedErrorMessage)
+        assert expectedErrorMessage == null || content['message'].toString().contains(expectedErrorMessage)
         assert expectedErrorDetails == null || content['details'].toString().contains(expectedErrorDetails)
     }
 
diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/util/TopicValidatorSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/util/TopicValidatorSpec.groovy
new file mode 100644 (file)
index 0000000..e626e15
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ *  ============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.rest.util
+
+import org.onap.cps.ncmp.rest.exceptions.InvalidTopicException
+import spock.lang.Specification
+
+class TopicValidatorSpec extends Specification {
+
+    def 'Valid topic name validation.'() {
+        when: 'a valid topic name is validated'
+            TopicValidator.validateTopicName('my-valid-topic')
+        then: 'no exception is thrown'
+            noExceptionThrown()
+    }
+
+    def 'Validating invalid topic names.'() {
+        when: 'the invalid topic name is validated'
+            TopicValidator.validateTopicName(topicName)
+        then: 'boolean response will be returned for #scenario'
+            thrown(InvalidTopicException)
+        where: 'the following names are used'
+            scenario                  | topicName
+            'empty topic'             | ''
+            'blank topic'             | ' '
+            'invalid non empty topic' | '1_5_*_#'
+    }
+
+}
index 45dba21..0ea0674 100644 (file)
@@ -33,6 +33,7 @@ import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters;
 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.FetchDescendantsOption;
 import org.onap.cps.spi.model.ModuleDefinition;
 import org.onap.cps.spi.model.ModuleReference;
 
@@ -66,6 +67,18 @@ public interface NetworkCmProxyDataService {
                                                  String topicParamInQuery,
                                                  String requestId);
 
+    /**
+     * Get resource data for operational.
+     *
+     * @param cmHandleId cm handle identifier
+     * @param resourceIdentifier resource identifier
+     * @Link FetchDescendantsOption fetch descendants option
+     * @return {@code Object} resource data
+     */
+    Object getResourceDataOperational(String cmHandleId,
+                                      String resourceIdentifier,
+                                      FetchDescendantsOption fetchDescendantsOption);
+
     /**
      * Get resource data for data store pass-through running
      * using dmi.
index 5b072f3..3f440d6 100755 (executable)
@@ -23,6 +23,7 @@
 
 package org.onap.cps.ncmp.api.impl;
 
+import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME;
 import static org.onap.cps.ncmp.api.impl.operations.DmiRequestBody.OperationEnum;
 import static org.onap.cps.utils.CmHandleQueryRestParametersValidator.validateCmHandleQueryParameters;
 
@@ -57,7 +58,8 @@ import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationErr
 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.FetchDescendantsOption;
+import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch;
 import org.onap.cps.spi.exceptions.CpsException;
 import org.onap.cps.spi.exceptions.DataNodeNotFoundException;
 import org.onap.cps.spi.exceptions.DataValidationException;
@@ -116,13 +118,21 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
                                                         final String topicParamInQuery,
                                                         final String requestId) {
         final ResponseEntity<?> responseEntity = dmiDataOperations.getResourceDataFromDmi(cmHandleId,
-            resourceIdentifier,
-            optionsParamInQuery,
-            DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL,
-            requestId, topicParamInQuery);
+                resourceIdentifier,
+                optionsParamInQuery,
+                DmiOperations.DataStoreEnum.PASSTHROUGH_OPERATIONAL,
+                requestId, topicParamInQuery);
         return responseEntity.getBody();
     }
 
+    @Override
+    public Object getResourceDataOperational(final String cmHandleId,
+                                             final String resourceIdentifier,
+                                             final FetchDescendantsOption fetchDescendantsOption) {
+        return cpsDataService.getDataNode(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId, resourceIdentifier,
+                fetchDescendantsOption);
+    }
+
     @Override
     public Object getResourceDataPassThroughRunningForCmHandle(final String cmHandleId,
                                                                final String resourceIdentifier,
@@ -130,10 +140,10 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
                                                                final String topicParamInQuery,
                                                                final String requestId) {
         final ResponseEntity<?> responseEntity = dmiDataOperations.getResourceDataFromDmi(cmHandleId,
-            resourceIdentifier,
-            optionsParamInQuery,
-            DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING,
-            requestId, topicParamInQuery);
+                resourceIdentifier,
+                optionsParamInQuery,
+                DmiOperations.DataStoreEnum.PASSTHROUGH_RUNNING,
+                requestId, topicParamInQuery);
         return responseEntity.getBody();
     }
 
@@ -145,7 +155,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
                                                                  final String dataType) {
         CpsValidator.validateNameCharacters(cmHandleId);
         return dmiDataOperations.writeResourceDataPassThroughRunningFromDmi(cmHandleId, resourceIdentifier, operation,
-            requestData, dataType);
+                requestData, dataType);
     }
 
     @Override
@@ -196,29 +206,29 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
      * Set the data sync enabled flag, along with the data sync state
      * based on the data sync enabled boolean for the cm handle id provided.
      *
-     * @param cmHandleId cm handle id
+     * @param cmHandleId      cm handle id
      * @param dataSyncEnabled data sync enabled flag
      */
     @Override
     public void setDataSyncEnabled(final String cmHandleId, final boolean dataSyncEnabled) {
         CpsValidator.validateNameCharacters(cmHandleId);
         final CompositeState compositeState = inventoryPersistence
-            .getCmHandleState(cmHandleId);
+                .getCmHandleState(cmHandleId);
         if (compositeState.getDataSyncEnabled().equals(dataSyncEnabled)) {
             log.info("Data-Sync Enabled flag is already: {} ", dataSyncEnabled);
         } else if (compositeState.getCmHandleState() != CmHandleState.READY) {
             throw new CpsException("State mismatch exception.", "Cm-Handle not in READY state. Cm handle state is: "
-                + compositeState.getCmHandleState());
+                    + compositeState.getCmHandleState());
         } else {
             final DataStoreSyncState dataStoreSyncState = compositeState.getDataStores()
-                .getOperationalDataStore().getDataStoreSyncState();
+                    .getOperationalDataStore().getDataStoreSyncState();
             if (!dataSyncEnabled && dataStoreSyncState == DataStoreSyncState.SYNCHRONIZED) {
-                cpsDataService.deleteDataNode("NFP-Operational", cmHandleId,
-                    "/netconf-state", OffsetDateTime.now());
+                cpsDataService.deleteDataNode(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId,
+                        "/netconf-state", OffsetDateTime.now());
             }
             CompositeStateUtils.setDataSyncEnabledFlagWithDataSyncState(dataSyncEnabled, compositeState);
             inventoryPersistence.saveCmHandleState(cmHandleId,
-                compositeState);
+                    compositeState);
         }
     }
 
@@ -233,9 +243,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
         final Set<NcmpServiceCmHandle> ncmpServiceCmHandles =
                 cmHandleQueries.getCmHandlesByDmiPluginIdentifier(dmiPluginIdentifier);
         final Set<String> cmHandleIds = new HashSet<>(ncmpServiceCmHandles.size());
-        ncmpServiceCmHandles.forEach(cmHandle -> {
-            cmHandleIds.add(cmHandle.getCmHandleId());
-        });
+        ncmpServiceCmHandles.forEach(cmHandle -> cmHandleIds.add(cmHandle.getCmHandleId()));
         return cmHandleIds;
     }
 
@@ -262,7 +270,7 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
     public Map<String, String> getCmHandlePublicProperties(final String cmHandleId) {
         CpsValidator.validateNameCharacters(cmHandleId);
         final YangModelCmHandle yangModelCmHandle =
-            inventoryPersistence.getYangModelCmHandle(cmHandleId);
+                inventoryPersistence.getYangModelCmHandle(cmHandleId);
         final List<YangModelCmHandle.Property> yangModelPublicProperties = yangModelCmHandle.getPublicProperties();
         final Map<String, String> cmHandlePublicProperties = new HashMap<>();
         YangDataConverter.asPropertiesMap(yangModelPublicProperties, cmHandlePublicProperties);
@@ -290,14 +298,18 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
     public List<CmHandleRegistrationResponse> parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(
             final DmiPluginRegistration dmiPluginRegistration) {
         List<CmHandleRegistrationResponse> cmHandleRegistrationResponses = new ArrayList<>();
+        final Map<YangModelCmHandle, CmHandleState> cmHandleStatePerCmHandle = new HashMap<>();
         try {
-            cmHandleRegistrationResponses = dmiPluginRegistration.getCreatedCmHandles().stream()
-                .map(cmHandle ->
-                    YangModelCmHandle.toYangModelCmHandle(
-                        dmiPluginRegistration.getDmiPlugin(),
-                        dmiPluginRegistration.getDmiDataPlugin(),
-                        dmiPluginRegistration.getDmiModelPlugin(),
-                        cmHandle)).map(this::registerNewCmHandle).collect(Collectors.toList());
+            dmiPluginRegistration.getCreatedCmHandles()
+                    .forEach(cmHandle -> {
+                        final YangModelCmHandle yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle(
+                                dmiPluginRegistration.getDmiPlugin(),
+                                dmiPluginRegistration.getDmiDataPlugin(),
+                                dmiPluginRegistration.getDmiModelPlugin(),
+                                cmHandle);
+                        cmHandleStatePerCmHandle.put(yangModelCmHandle, CmHandleState.ADVISED);
+                    });
+            cmHandleRegistrationResponses = registerNewCmHandles(cmHandleStatePerCmHandle);
         } catch (final DataValidationException dataValidationException) {
             cmHandleRegistrationResponses.add(CmHandleRegistrationResponse.createFailureResponse(dmiPluginRegistration
                             .getCreatedCmHandles().stream()
@@ -346,15 +358,19 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
         inventoryPersistence.deleteListOrListElement("/dmi-registry/cm-handles[@id='" + cmHandleId + "']");
     }
 
-    private CmHandleRegistrationResponse registerNewCmHandle(final YangModelCmHandle yangModelCmHandle) {
+    private List<CmHandleRegistrationResponse> registerNewCmHandles(final Map<YangModelCmHandle, CmHandleState>
+                                                                            cmHandleStatePerCmHandle) {
+        final List<String> cmHandleIds = cmHandleStatePerCmHandle.keySet().stream().map(YangModelCmHandle::getId)
+                .collect(Collectors.toList());
         try {
-            lcmEventsCmHandleStateHandler.updateCmHandleState(yangModelCmHandle, CmHandleState.ADVISED);
-            return CmHandleRegistrationResponse.createSuccessResponse(yangModelCmHandle.getId());
-        } catch (final AlreadyDefinedException alreadyDefinedException) {
-            return CmHandleRegistrationResponse.createFailureResponse(
-                    yangModelCmHandle.getId(), RegistrationError.CM_HANDLE_ALREADY_EXIST);
+            lcmEventsCmHandleStateHandler.updateCmHandleStateBatch(cmHandleStatePerCmHandle);
+            return CmHandleRegistrationResponse.createSuccessResponses(cmHandleIds);
+        } catch (final AlreadyDefinedExceptionBatch alreadyDefinedExceptionBatch) {
+            return CmHandleRegistrationResponse.createFailureResponses(
+                    alreadyDefinedExceptionBatch.getAlreadyDefinedXpaths(),
+                    RegistrationError.CM_HANDLE_ALREADY_EXIST);
         } catch (final Exception exception) {
-            return CmHandleRegistrationResponse.createFailureResponse(yangModelCmHandle.getId(), exception);
+            return CmHandleRegistrationResponse.createFailureResponses(cmHandleIds, exception);
         }
     }
 }
index d457f26..d5b459b 100644 (file)
@@ -56,7 +56,7 @@ public class DmiRestClient {
         } catch (final HttpStatusCodeException httpStatusCodeException) {
             final String exceptionMessage = "Unable to " + operation.toString() + " resource data.";
             throw new HttpClientRequestException(exceptionMessage, httpStatusCodeException.getResponseBodyAsString(),
-                httpStatusCodeException.getRawStatusCode());
+                    httpStatusCodeException.getRawStatusCode());
         }
     }
 
index 45e2754..f842ddb 100644 (file)
@@ -30,6 +30,7 @@ import java.util.List;
 import java.util.Map;
 import lombok.AllArgsConstructor;
 import lombok.Data;
+import lombok.EqualsAndHashCode;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.Setter;
@@ -46,8 +47,10 @@ import org.onap.cps.utils.CpsValidator;
 @Setter
 @NoArgsConstructor
 @JsonInclude(Include.NON_NULL)
+@EqualsAndHashCode(onlyExplicitlyIncluded = true)
 public class YangModelCmHandle {
 
+    @EqualsAndHashCode.Include
     private String id;
 
     @JsonProperty("dmi-service-name")
index 569e91e..daabbb5 100644 (file)
 
 package org.onap.cps.ncmp.api.inventory;
 
-import static org.onap.cps.ncmp.api.impl.utils.YangDataConverter.convertYangModelCmHandleToNcmpServiceCmHandle;
-import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS;
-import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.stream.Collectors;
-import lombok.RequiredArgsConstructor;
-import org.onap.cps.ncmp.api.impl.utils.YangDataConverter;
 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
-import org.onap.cps.spi.CpsDataPersistenceService;
 import org.onap.cps.spi.FetchDescendantsOption;
 import org.onap.cps.spi.model.DataNode;
-import org.springframework.stereotype.Component;
-
-@RequiredArgsConstructor
-@Component
-public class CmHandleQueries {
-
-    private static final String NCMP_DATASPACE_NAME = "NCMP-Admin";
-    private static final String NCMP_DMI_REGISTRY_ANCHOR = "ncmp-dmi-registry";
-
-    private final CpsDataPersistenceService cpsDataPersistenceService;
-    private static final Map<String, NcmpServiceCmHandle> NO_QUERY_TO_EXECUTE = null;
-    private static final String ANCESTOR_CM_HANDLES = "/ancestor::cm-handles";
 
+public interface CmHandleQueries {
 
     /**
      * Query CmHandles based on PublicProperties.
@@ -58,52 +35,17 @@ public class CmHandleQueries {
      * @param publicPropertyQueryPairs public properties for query
      * @return CmHandles which have these public properties
      */
-    public Map<String, NcmpServiceCmHandle> queryCmHandlePublicProperties(
-            final Map<String, String> publicPropertyQueryPairs) {
-        if (publicPropertyQueryPairs.isEmpty()) {
-            return Collections.emptyMap();
-        }
-        Map<String, NcmpServiceCmHandle> cmHandleIdToNcmpServiceCmHandles = null;
-        for (final Map.Entry<String, String> publicPropertyQueryPair : publicPropertyQueryPairs.entrySet()) {
-            final String cpsPath = "//public-properties[@name=\"" + publicPropertyQueryPair.getKey()
-                    + "\" and @value=\"" + publicPropertyQueryPair.getValue() + "\"]";
-
-            final Collection<DataNode> dataNodes = queryCmHandleDataNodesByCpsPath(cpsPath, INCLUDE_ALL_DESCENDANTS);
-            if (cmHandleIdToNcmpServiceCmHandles == null) {
-                cmHandleIdToNcmpServiceCmHandles = collectDataNodesToNcmpServiceCmHandles(dataNodes);
-            } else {
-                final Collection<String> cmHandleIdsToRetain = dataNodes.parallelStream()
-                        .map(dataNode -> dataNode.getLeaves().get("id").toString()).collect(Collectors.toSet());
-                cmHandleIdToNcmpServiceCmHandles.keySet().retainAll(cmHandleIdsToRetain);
-            }
-            if (cmHandleIdToNcmpServiceCmHandles.isEmpty()) {
-                break;
-            }
-        }
-        return cmHandleIdToNcmpServiceCmHandles;
-    }
+    Map<String, NcmpServiceCmHandle> queryCmHandlePublicProperties(Map<String, String> publicPropertyQueryPairs);
 
     /**
      * Combine Maps of CmHandles.
      *
-     * @param firstQuery first CmHandles Map
+     * @param firstQuery  first CmHandles Map
      * @param secondQuery second CmHandles Map
      * @return combined Map of CmHandles
      */
-    public Map<String, NcmpServiceCmHandle> combineCmHandleQueries(
-            final Map<String, NcmpServiceCmHandle> firstQuery,
-            final Map<String, NcmpServiceCmHandle> secondQuery) {
-        if (firstQuery == NO_QUERY_TO_EXECUTE && secondQuery == NO_QUERY_TO_EXECUTE) {
-            return NO_QUERY_TO_EXECUTE;
-        } else if (firstQuery == NO_QUERY_TO_EXECUTE) {
-            return secondQuery;
-        } else if (secondQuery == NO_QUERY_TO_EXECUTE) {
-            return firstQuery;
-        } else {
-            firstQuery.keySet().retainAll(secondQuery.keySet());
-            return firstQuery;
-        }
-    }
+    Map<String, NcmpServiceCmHandle> combineCmHandleQueries(Map<String, NcmpServiceCmHandle> firstQuery,
+            Map<String, NcmpServiceCmHandle> secondQuery);
 
     /**
      * Method which returns cm handles by the cm handles state.
@@ -111,10 +53,7 @@ public class CmHandleQueries {
      * @param cmHandleState cm handle state
      * @return a list of cm handles
      */
-    public List<DataNode> queryCmHandlesByState(final CmHandleState cmHandleState) {
-        return queryCmHandleDataNodesByCpsPath("//state[@cm-handle-state=\"" + cmHandleState + "\"]",
-            INCLUDE_ALL_DESCENDANTS);
-    }
+    List<DataNode> queryCmHandlesByState(CmHandleState cmHandleState);
 
     /**
      * Method to return data nodes representing the cm handles.
@@ -122,49 +61,24 @@ public class CmHandleQueries {
      * @param cpsPath cps path for which the cmHandle is requested
      * @return a list of data nodes representing the cm handles.
      */
-    public List<DataNode> queryCmHandleDataNodesByCpsPath(final String cpsPath,
-                                                          final FetchDescendantsOption fetchDescendantsOption) {
-        return cpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-            cpsPath + ANCESTOR_CM_HANDLES, fetchDescendantsOption);
-    }
+    List<DataNode> queryCmHandleDataNodesByCpsPath(String cpsPath, FetchDescendantsOption fetchDescendantsOption);
 
     /**
      * Method to check the state of a cm handle with given id.
      *
-     * @param cmHandleId cm handle id
+     * @param cmHandleId            cm handle id
      * @param requiredCmHandleState the required state of the cm handle
      * @return a boolean, true if the state is equal to the required state
      */
-    public boolean cmHandleHasState(final String cmHandleId, final CmHandleState requiredCmHandleState) {
-        final DataNode stateDataNode = getCmHandleState(cmHandleId);
-        final String cmHandleStateAsString = (String) stateDataNode.getLeaves().get("cm-handle-state");
-        return CmHandleState.valueOf(cmHandleStateAsString).equals(requiredCmHandleState);
-    }
+    boolean cmHandleHasState(String cmHandleId, CmHandleState requiredCmHandleState);
 
     /**
      * Method which returns cm handles by the operational sync state of cm handle.
+     *
      * @param dataStoreSyncState sync state
      * @return a list of cm handles
      */
-    public List<DataNode> queryCmHandlesByOperationalSyncState(final DataStoreSyncState dataStoreSyncState) {
-        return queryCmHandleDataNodesByCpsPath("//state/datastores" + "/operational[@sync-state=\""
-                + dataStoreSyncState + "\"]", FetchDescendantsOption.OMIT_DESCENDANTS);
-    }
-
-    private Map<String, NcmpServiceCmHandle> collectDataNodesToNcmpServiceCmHandles(
-            final Collection<DataNode> dataNodes) {
-        final Map<String, NcmpServiceCmHandle> cmHandleIdToNcmpServiceCmHandle = new HashMap<>();
-        dataNodes.forEach(dataNode -> {
-            final NcmpServiceCmHandle ncmpServiceCmHandle = createNcmpServiceCmHandle(dataNode);
-            cmHandleIdToNcmpServiceCmHandle.put(ncmpServiceCmHandle.getCmHandleId(), ncmpServiceCmHandle);
-        });
-        return cmHandleIdToNcmpServiceCmHandle;
-    }
-
-    private NcmpServiceCmHandle createNcmpServiceCmHandle(final DataNode dataNode) {
-        return convertYangModelCmHandleToNcmpServiceCmHandle(YangDataConverter
-                .convertCmHandleToYangModel(dataNode, dataNode.getLeaves().get("id").toString()));
-    }
+    List<DataNode> queryCmHandlesByOperationalSyncState(DataStoreSyncState dataStoreSyncState);
 
     /**
      * Get all cm handles by DMI plugin identifier.
@@ -172,34 +86,5 @@ public class CmHandleQueries {
      * @param dmiPluginIdentifier DMI plugin identifier
      * @return set of cm handles
      */
-    public Set<NcmpServiceCmHandle> getCmHandlesByDmiPluginIdentifier(final String dmiPluginIdentifier) {
-        final Map<String, DataNode> cmHandleAsDataNodePerCmHandleId = new HashMap<>();
-        for (final ModelledDmiServiceLeaves modelledDmiServiceLeaf : ModelledDmiServiceLeaves.values()) {
-            for (final DataNode cmHandleAsDataNode: getCmHandlesByDmiPluginIdentifierAndDmiProperty(
-                    dmiPluginIdentifier,
-                    modelledDmiServiceLeaf.getLeafName())) {
-                cmHandleAsDataNodePerCmHandleId.put(
-                        cmHandleAsDataNode.getLeaves().get("id").toString(), cmHandleAsDataNode);
-            }
-        }
-        final Set<NcmpServiceCmHandle> ncmpServiceCmHandles = new HashSet<>(cmHandleAsDataNodePerCmHandleId.size());
-        cmHandleAsDataNodePerCmHandleId.values().forEach(
-                cmHandleAsDataNode -> ncmpServiceCmHandles.add(createNcmpServiceCmHandle(cmHandleAsDataNode)));
-        return ncmpServiceCmHandles;
-    }
-
-    private List<DataNode> getCmHandlesByDmiPluginIdentifierAndDmiProperty(final String dmiPluginIdentifier,
-                                                             final String dmiProperty) {
-        return cpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-                "/dmi-registry/cm-handles[@" + dmiProperty + "='" + dmiPluginIdentifier + "']",
-                OMIT_DESCENDANTS);
-    }
-
-    private DataNode getCmHandleState(final String cmHandleId) {
-        final String xpath = "/dmi-registry/cm-handles[@id='" + cmHandleId + "']/state";
-        return cpsDataPersistenceService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-                xpath, OMIT_DESCENDANTS);
-    }
+    Set<NcmpServiceCmHandle> getCmHandlesByDmiPluginIdentifier(String dmiPluginIdentifier);
 }
-
-
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CmHandleQueriesImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/CmHandleQueriesImpl.java
new file mode 100644 (file)
index 0000000..e9e2fca
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ *  ============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.inventory;
+
+import static org.onap.cps.ncmp.api.impl.utils.YangDataConverter.convertYangModelCmHandleToNcmpServiceCmHandle;
+import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS;
+import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import lombok.RequiredArgsConstructor;
+import org.onap.cps.ncmp.api.impl.utils.YangDataConverter;
+import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle;
+import org.onap.cps.spi.CpsDataPersistenceService;
+import org.onap.cps.spi.FetchDescendantsOption;
+import org.onap.cps.spi.model.DataNode;
+import org.springframework.stereotype.Component;
+
+@RequiredArgsConstructor
+@Component
+public class CmHandleQueriesImpl implements CmHandleQueries {
+
+    private static final String NCMP_DATASPACE_NAME = "NCMP-Admin";
+    private static final String NCMP_DMI_REGISTRY_ANCHOR = "ncmp-dmi-registry";
+
+    private final CpsDataPersistenceService cpsDataPersistenceService;
+    private static final Map<String, NcmpServiceCmHandle> NO_QUERY_TO_EXECUTE = null;
+    private static final String ANCESTOR_CM_HANDLES = "/ancestor::cm-handles";
+
+
+    @Override
+    public Map<String, NcmpServiceCmHandle> queryCmHandlePublicProperties(
+            final Map<String, String> publicPropertyQueryPairs) {
+        if (publicPropertyQueryPairs.isEmpty()) {
+            return Collections.emptyMap();
+        }
+        Map<String, NcmpServiceCmHandle> cmHandleIdToNcmpServiceCmHandles = null;
+        for (final Map.Entry<String, String> publicPropertyQueryPair : publicPropertyQueryPairs.entrySet()) {
+            final String cpsPath = "//public-properties[@name=\"" + publicPropertyQueryPair.getKey()
+                    + "\" and @value=\"" + publicPropertyQueryPair.getValue() + "\"]";
+
+            final Collection<DataNode> dataNodes = queryCmHandleDataNodesByCpsPath(cpsPath, INCLUDE_ALL_DESCENDANTS);
+            if (cmHandleIdToNcmpServiceCmHandles == null) {
+                cmHandleIdToNcmpServiceCmHandles = collectDataNodesToNcmpServiceCmHandles(dataNodes);
+            } else {
+                final Collection<String> cmHandleIdsToRetain = dataNodes.parallelStream()
+                        .map(dataNode -> dataNode.getLeaves().get("id").toString()).collect(Collectors.toSet());
+                cmHandleIdToNcmpServiceCmHandles.keySet().retainAll(cmHandleIdsToRetain);
+            }
+            if (cmHandleIdToNcmpServiceCmHandles.isEmpty()) {
+                break;
+            }
+        }
+        return cmHandleIdToNcmpServiceCmHandles;
+    }
+
+    @Override
+    public Map<String, NcmpServiceCmHandle> combineCmHandleQueries(final Map<String, NcmpServiceCmHandle> firstQuery,
+            final Map<String, NcmpServiceCmHandle> secondQuery) {
+        if (firstQuery == NO_QUERY_TO_EXECUTE && secondQuery == NO_QUERY_TO_EXECUTE) {
+            return NO_QUERY_TO_EXECUTE;
+        } else if (firstQuery == NO_QUERY_TO_EXECUTE) {
+            return secondQuery;
+        } else if (secondQuery == NO_QUERY_TO_EXECUTE) {
+            return firstQuery;
+        } else {
+            firstQuery.keySet().retainAll(secondQuery.keySet());
+            return firstQuery;
+        }
+    }
+
+    @Override
+    public List<DataNode> queryCmHandlesByState(final CmHandleState cmHandleState) {
+        return queryCmHandleDataNodesByCpsPath("//state[@cm-handle-state=\"" + cmHandleState + "\"]",
+            INCLUDE_ALL_DESCENDANTS);
+    }
+
+    @Override
+    public List<DataNode> queryCmHandleDataNodesByCpsPath(final String cpsPath,
+            final FetchDescendantsOption fetchDescendantsOption) {
+        return cpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+            cpsPath + ANCESTOR_CM_HANDLES, fetchDescendantsOption);
+    }
+
+    @Override
+    public boolean cmHandleHasState(final String cmHandleId, final CmHandleState requiredCmHandleState) {
+        final DataNode stateDataNode = getCmHandleState(cmHandleId);
+        final String cmHandleStateAsString = (String) stateDataNode.getLeaves().get("cm-handle-state");
+        return CmHandleState.valueOf(cmHandleStateAsString).equals(requiredCmHandleState);
+    }
+
+    @Override
+    public List<DataNode> queryCmHandlesByOperationalSyncState(final DataStoreSyncState dataStoreSyncState) {
+        return queryCmHandleDataNodesByCpsPath("//state/datastores" + "/operational[@sync-state=\""
+                + dataStoreSyncState + "\"]", FetchDescendantsOption.OMIT_DESCENDANTS);
+    }
+
+    private Map<String, NcmpServiceCmHandle> collectDataNodesToNcmpServiceCmHandles(
+            final Collection<DataNode> dataNodes) {
+        final Map<String, NcmpServiceCmHandle> cmHandleIdToNcmpServiceCmHandle = new HashMap<>();
+        dataNodes.forEach(dataNode -> {
+            final NcmpServiceCmHandle ncmpServiceCmHandle = createNcmpServiceCmHandle(dataNode);
+            cmHandleIdToNcmpServiceCmHandle.put(ncmpServiceCmHandle.getCmHandleId(), ncmpServiceCmHandle);
+        });
+        return cmHandleIdToNcmpServiceCmHandle;
+    }
+
+    private NcmpServiceCmHandle createNcmpServiceCmHandle(final DataNode dataNode) {
+        return convertYangModelCmHandleToNcmpServiceCmHandle(YangDataConverter
+                .convertCmHandleToYangModel(dataNode, dataNode.getLeaves().get("id").toString()));
+    }
+
+    @Override
+    public Set<NcmpServiceCmHandle> getCmHandlesByDmiPluginIdentifier(final String dmiPluginIdentifier) {
+        final Map<String, DataNode> cmHandleAsDataNodePerCmHandleId = new HashMap<>();
+        for (final ModelledDmiServiceLeaves modelledDmiServiceLeaf : ModelledDmiServiceLeaves.values()) {
+            for (final DataNode cmHandleAsDataNode: getCmHandlesByDmiPluginIdentifierAndDmiProperty(
+                    dmiPluginIdentifier,
+                    modelledDmiServiceLeaf.getLeafName())) {
+                cmHandleAsDataNodePerCmHandleId.put(
+                        cmHandleAsDataNode.getLeaves().get("id").toString(), cmHandleAsDataNode);
+            }
+        }
+        final Set<NcmpServiceCmHandle> ncmpServiceCmHandles = new HashSet<>(cmHandleAsDataNodePerCmHandleId.size());
+        cmHandleAsDataNodePerCmHandleId.values().forEach(
+                cmHandleAsDataNode -> ncmpServiceCmHandles.add(createNcmpServiceCmHandle(cmHandleAsDataNode)));
+        return ncmpServiceCmHandles;
+    }
+
+    private List<DataNode> getCmHandlesByDmiPluginIdentifierAndDmiProperty(final String dmiPluginIdentifier,
+                                                             final String dmiProperty) {
+        return cpsDataPersistenceService.queryDataNodes(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+                "/dmi-registry/cm-handles[@" + dmiProperty + "='" + dmiPluginIdentifier + "']",
+                OMIT_DESCENDANTS);
+    }
+
+    private DataNode getCmHandleState(final String cmHandleId) {
+        final String xpath = "/dmi-registry/cm-handles[@id='" + cmHandleId + "']/state";
+        return cpsDataPersistenceService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+                xpath, OMIT_DESCENDANTS);
+    }
+}
+
+
index 9174dc7..bfc3a9a 100644 (file)
@@ -1,7 +1,6 @@
 /*
  *  ============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.inventory;
 
-import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME;
-import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NO_TIMESTAMP;
-import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED;
-import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS;
-
-import java.time.OffsetDateTime;
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.onap.cps.api.CpsDataService;
-import org.onap.cps.api.CpsModuleService;
-import org.onap.cps.ncmp.api.impl.utils.YangDataConverter;
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
-import org.onap.cps.spi.CpsAdminPersistenceService;
-import org.onap.cps.spi.CpsDataPersistenceService;
-import org.onap.cps.spi.FetchDescendantsOption;
-import org.onap.cps.spi.exceptions.SchemaSetNotFoundException;
 import org.onap.cps.spi.model.Anchor;
 import org.onap.cps.spi.model.DataNode;
 import org.onap.cps.spi.model.ModuleDefinition;
 import org.onap.cps.spi.model.ModuleReference;
-import org.onap.cps.utils.CpsValidator;
-import org.onap.cps.utils.JsonObjectMapper;
-import org.springframework.stereotype.Component;
-
-@Slf4j
-@RequiredArgsConstructor
-@Component
-public class InventoryPersistence {
-
-    private static final String NCMP_DATASPACE_NAME = "NCMP-Admin";
-
-    private static final String NCMP_DMI_REGISTRY_ANCHOR = "ncmp-dmi-registry";
-
-    private static final String NCMP_DMI_REGISTRY_PARENT = "/dmi-registry";
-
-    private static final String CM_HANDLE_XPATH_TEMPLATE = "/dmi-registry/cm-handles[@id='" + "%s" + "']";
 
-    private final JsonObjectMapper jsonObjectMapper;
-
-    private final CpsDataService cpsDataService;
-
-    private final CpsModuleService cpsModuleService;
-
-    private final CpsDataPersistenceService cpsDataPersistenceService;
-
-    private final CpsAdminPersistenceService cpsAdminPersistenceService;
+public interface InventoryPersistence {
 
     /**
      * Get the Cm Handle Composite State from the data node.
@@ -79,50 +36,30 @@ public class InventoryPersistence {
      * @param cmHandleId cm handle id
      * @return the cm handle composite state
      */
-    public CompositeState getCmHandleState(final String cmHandleId) {
-        final DataNode stateAsDataNode = cpsDataService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-            String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId) + "/state",
-            FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS);
-        return new CompositeStateBuilder().fromDataNode(stateAsDataNode).build();
-    }
+    CompositeState getCmHandleState(String cmHandleId);
 
     /**
      * Save the cm handles state.
      *
-     * @param cmHandleId    cm handle id
+     * @param cmHandleId     cm handle id
      * @param compositeState composite state
      */
-    public void saveCmHandleState(final String cmHandleId, final CompositeState compositeState) {
-        final String cmHandleJsonData = String.format("{\"state\":%s}",
-            jsonObjectMapper.asJsonString(compositeState));
-        cpsDataService.updateDataNodeAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-            String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId),
-            cmHandleJsonData, OffsetDateTime.now());
-    }
+    void saveCmHandleState(String cmHandleId, CompositeState compositeState);
 
     /**
      * Save all cm handles states in batch.
      *
      * @param cmHandleStatePerCmHandleId contains cm handle id and updated state
      */
-    public void saveCmHandleStateBatch(final Map<String, CompositeState> cmHandleStatePerCmHandleId) {
-        final Map<String, String> cmHandlesJsonDataMap = new HashMap<>();
-        cmHandleStatePerCmHandleId.forEach((cmHandleId, compositeState) -> cmHandlesJsonDataMap.put(
-                String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId),
-                String.format("{\"state\":%s}", jsonObjectMapper.asJsonString(compositeState))));
-        cpsDataService.updateDataNodesAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-                cmHandlesJsonDataMap, OffsetDateTime.now());
-    }
+    void saveCmHandleStateBatch(Map<String, CompositeState> cmHandleStatePerCmHandleId);
 
     /**
      * This method retrieves DMI service name, DMI properties and the state for a given cm handle.
+     *
      * @param cmHandleId the id of the cm handle
      * @return yang model cm handle
      */
-    public YangModelCmHandle getYangModelCmHandle(final String cmHandleId) {
-        CpsValidator.validateNameCharacters(cmHandleId);
-        return YangDataConverter.convertCmHandleToYangModel(getCmHandleDataNode(cmHandleId), cmHandleId);
-    }
+    YangModelCmHandle getYangModelCmHandle(String cmHandleId);
 
     /**
      * Method to return module definitions by cmHandleId.
@@ -130,9 +67,7 @@ public class InventoryPersistence {
      * @param cmHandleId cm handle ID
      * @return a collection of module definitions (moduleName, revision and yang resource content)
      */
-    public Collection<ModuleDefinition> getModuleDefinitionsByCmHandleId(final String cmHandleId) {
-        return cpsModuleService.getModuleDefinitionsByAnchorName(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId);
-    }
+    Collection<ModuleDefinition> getModuleDefinitionsByCmHandleId(String cmHandleId);
 
     /**
      * Method to return module references by cmHandleId.
@@ -140,60 +75,35 @@ public class InventoryPersistence {
      * @param cmHandleId cm handle ID
      * @return a collection of module references (moduleName and revision)
      */
-    public Collection<ModuleReference> getYangResourcesModuleReferences(final String cmHandleId) {
-        CpsValidator.validateNameCharacters(cmHandleId);
-        return cpsModuleService.getYangResourcesModuleReferences(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId);
-    }
+    Collection<ModuleReference> getYangResourcesModuleReferences(String cmHandleId);
 
     /**
      * Method to save cmHandle.
      *
      * @param yangModelCmHandle cmHandle represented as Yang Model
      */
-    public void saveCmHandle(final YangModelCmHandle yangModelCmHandle) {
-        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);
-    }
+    void saveCmHandle(YangModelCmHandle yangModelCmHandle);
 
     /**
      * Method to save batch of cm handles.
      *
      * @param yangModelCmHandles cm handle represented as Yang Models
      */
-    public void saveCmHandleBatch(final Collection<YangModelCmHandle> yangModelCmHandles) {
-        final List<String> cmHandlesJsonData = new ArrayList<>();
-        yangModelCmHandles.forEach(yangModelCmHandle -> cmHandlesJsonData.add(
-                String.format("{\"cm-handles\":[%s]}", jsonObjectMapper.asJsonString(yangModelCmHandle))));
-        cpsDataService.saveListElementsBatch(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-                NCMP_DMI_REGISTRY_PARENT, cmHandlesJsonData, NO_TIMESTAMP);
-    }
+    void saveCmHandleBatch(Collection<YangModelCmHandle> yangModelCmHandles);
 
     /**
      * Method to delete a list or a list element.
      *
      * @param listElementXpath list element xPath
      */
-    public void deleteListOrListElement(final String listElementXpath) {
-        cpsDataService.deleteListOrListElement(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-                listElementXpath, NO_TIMESTAMP);
-    }
+    void deleteListOrListElement(String listElementXpath);
 
     /**
      * Method to delete a schema set.
      *
      * @param schemaSetName schema set name
      */
-    public void deleteSchemaSetWithCascade(final String schemaSetName) {
-        try {
-            CpsValidator.validateNameCharacters(schemaSetName);
-            cpsModuleService.deleteSchemaSet(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, schemaSetName,
-                    CASCADE_DELETE_ALLOWED);
-        } catch (final SchemaSetNotFoundException schemaSetNotFoundException) {
-            log.warn("Schema set {} does not exist or already deleted", schemaSetName);
-        }
-    }
+    void deleteSchemaSetWithCascade(String schemaSetName);
 
     /**
      * Get data node via xpath.
@@ -201,10 +111,7 @@ public class InventoryPersistence {
      * @param xpath xpath
      * @return data node
      */
-    public DataNode getDataNode(final String xpath) {
-        return cpsDataPersistenceService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-                xpath, INCLUDE_ALL_DESCENDANTS);
-    }
+    DataNode getDataNode(String xpath);
 
     /**
      * Get data node of given cm handle.
@@ -212,9 +119,7 @@ public class InventoryPersistence {
      * @param cmHandleId cmHandle ID
      * @return data node
      */
-    public DataNode getCmHandleDataNode(final String cmHandleId) {
-        return this.getDataNode(String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId));
-    }
+    DataNode getCmHandleDataNode(String cmHandleId);
 
     /**
      * Query anchors via module names.
@@ -222,37 +127,27 @@ public class InventoryPersistence {
      * @param moduleNamesForQuery module names
      * @return Collection of anchors
      */
-    public Collection<Anchor> queryAnchors(final Collection<String> moduleNamesForQuery) {
-        return  cpsAdminPersistenceService.queryAnchors(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, moduleNamesForQuery);
-    }
+    Collection<Anchor> queryAnchors(Collection<String> moduleNamesForQuery);
 
     /**
      * Method to get all anchors.
      *
      * @return Collection of anchors
      */
-    public Collection<Anchor> getAnchors() {
-        return cpsAdminPersistenceService.getAnchors(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME);
-    }
+    Collection<Anchor> getAnchors();
 
     /**
      * Replaces list content by removing all existing elements and inserting the given new elements as data nodes.
      *
-     * @param parentNodeXpath   parent node xpath
-     * @param dataNodes         datanodes representing the updated data
+     * @param parentNodeXpath parent node xpath
+     * @param dataNodes       datanodes representing the updated data
      */
-    public void replaceListContent(final String parentNodeXpath, final Collection<DataNode> dataNodes) {
-        cpsDataService.replaceListContent(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
-                parentNodeXpath, dataNodes, NO_TIMESTAMP);
-    }
+    void replaceListContent(String parentNodeXpath, Collection<DataNode> dataNodes);
 
     /**
      * Deletes data node for given anchor and dataspace.
      *
      * @param dataNodeXpath data node xpath
      */
-    public void deleteDataNode(final String dataNodeXpath) {
-        cpsDataService.deleteDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, dataNodeXpath,
-                NO_TIMESTAMP);
-    }
-}
\ No newline at end of file
+    void deleteDataNode(String dataNodeXpath);
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/InventoryPersistenceImpl.java
new file mode 100644 (file)
index 0000000..99edfdb
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ *  ============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.
+ *  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.inventory;
+
+import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME;
+import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NO_TIMESTAMP;
+import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED;
+import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS;
+
+import java.time.OffsetDateTime;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.api.CpsDataService;
+import org.onap.cps.api.CpsModuleService;
+import org.onap.cps.ncmp.api.impl.utils.YangDataConverter;
+import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
+import org.onap.cps.spi.CpsAdminPersistenceService;
+import org.onap.cps.spi.CpsDataPersistenceService;
+import org.onap.cps.spi.FetchDescendantsOption;
+import org.onap.cps.spi.exceptions.SchemaSetNotFoundException;
+import org.onap.cps.spi.model.Anchor;
+import org.onap.cps.spi.model.DataNode;
+import org.onap.cps.spi.model.ModuleDefinition;
+import org.onap.cps.spi.model.ModuleReference;
+import org.onap.cps.utils.CpsValidator;
+import org.onap.cps.utils.JsonObjectMapper;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@RequiredArgsConstructor
+@Component
+public class InventoryPersistenceImpl implements InventoryPersistence {
+
+    private static final String NCMP_DATASPACE_NAME = "NCMP-Admin";
+
+    private static final String NCMP_DMI_REGISTRY_ANCHOR = "ncmp-dmi-registry";
+
+    private static final String NCMP_DMI_REGISTRY_PARENT = "/dmi-registry";
+
+    private static final String CM_HANDLE_XPATH_TEMPLATE = "/dmi-registry/cm-handles[@id='" + "%s" + "']";
+
+    private final JsonObjectMapper jsonObjectMapper;
+
+    private final CpsDataService cpsDataService;
+
+    private final CpsModuleService cpsModuleService;
+
+    private final CpsDataPersistenceService cpsDataPersistenceService;
+
+    private final CpsAdminPersistenceService cpsAdminPersistenceService;
+
+    @Override
+    public CompositeState getCmHandleState(final String cmHandleId) {
+        final DataNode stateAsDataNode = cpsDataService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+            String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId) + "/state",
+            FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS);
+        return new CompositeStateBuilder().fromDataNode(stateAsDataNode).build();
+    }
+
+    @Override
+    public void saveCmHandleState(final String cmHandleId, final CompositeState compositeState) {
+        final String cmHandleJsonData = String.format("{\"state\":%s}",
+            jsonObjectMapper.asJsonString(compositeState));
+        cpsDataService.updateDataNodeAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+            String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId),
+            cmHandleJsonData, OffsetDateTime.now());
+    }
+
+    @Override
+    public void saveCmHandleStateBatch(final Map<String, CompositeState> cmHandleStatePerCmHandleId) {
+        final Map<String, String> cmHandlesJsonDataMap = new HashMap<>();
+        cmHandleStatePerCmHandleId.forEach((cmHandleId, compositeState) -> cmHandlesJsonDataMap.put(
+                String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId),
+                String.format("{\"state\":%s}", jsonObjectMapper.asJsonString(compositeState))));
+        cpsDataService.updateDataNodesAndDescendants(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+                cmHandlesJsonDataMap, OffsetDateTime.now());
+    }
+
+    @Override
+    public YangModelCmHandle getYangModelCmHandle(final String cmHandleId) {
+        CpsValidator.validateNameCharacters(cmHandleId);
+        return YangDataConverter.convertCmHandleToYangModel(getCmHandleDataNode(cmHandleId), cmHandleId);
+    }
+
+    @Override
+    public Collection<ModuleDefinition> getModuleDefinitionsByCmHandleId(final String cmHandleId) {
+        return cpsModuleService.getModuleDefinitionsByAnchorName(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId);
+    }
+
+    @Override
+    public Collection<ModuleReference> getYangResourcesModuleReferences(final String cmHandleId) {
+        CpsValidator.validateNameCharacters(cmHandleId);
+        return cpsModuleService.getYangResourcesModuleReferences(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId);
+    }
+
+    @Override
+    public void saveCmHandle(final YangModelCmHandle yangModelCmHandle) {
+        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);
+    }
+
+    @Override
+    public void saveCmHandleBatch(final Collection<YangModelCmHandle> yangModelCmHandles) {
+        final List<String> cmHandlesJsonData = new ArrayList<>();
+        yangModelCmHandles.forEach(yangModelCmHandle -> cmHandlesJsonData.add(
+                String.format("{\"cm-handles\":[%s]}", jsonObjectMapper.asJsonString(yangModelCmHandle))));
+        cpsDataService.saveListElementsBatch(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+                NCMP_DMI_REGISTRY_PARENT, cmHandlesJsonData, NO_TIMESTAMP);
+    }
+
+    @Override
+    public void deleteListOrListElement(final String listElementXpath) {
+        cpsDataService.deleteListOrListElement(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+                listElementXpath, NO_TIMESTAMP);
+    }
+
+    @Override
+    public void deleteSchemaSetWithCascade(final String schemaSetName) {
+        try {
+            CpsValidator.validateNameCharacters(schemaSetName);
+            cpsModuleService.deleteSchemaSet(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, schemaSetName,
+                    CASCADE_DELETE_ALLOWED);
+        } catch (final SchemaSetNotFoundException schemaSetNotFoundException) {
+            log.warn("Schema set {} does not exist or already deleted", schemaSetName);
+        }
+    }
+
+    @Override
+    public DataNode getDataNode(final String xpath) {
+        return cpsDataPersistenceService.getDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+                xpath, INCLUDE_ALL_DESCENDANTS);
+    }
+
+    @Override
+    public DataNode getCmHandleDataNode(final String cmHandleId) {
+        return this.getDataNode(String.format(CM_HANDLE_XPATH_TEMPLATE, cmHandleId));
+    }
+
+    @Override
+    public Collection<Anchor> queryAnchors(final Collection<String> moduleNamesForQuery) {
+        return  cpsAdminPersistenceService.queryAnchors(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, moduleNamesForQuery);
+    }
+
+    @Override
+    public Collection<Anchor> getAnchors() {
+        return cpsAdminPersistenceService.getAnchors(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME);
+    }
+
+    @Override
+    public void replaceListContent(final String parentNodeXpath, final Collection<DataNode> dataNodes) {
+        cpsDataService.replaceListContent(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR,
+                parentNodeXpath, dataNodes, NO_TIMESTAMP);
+    }
+
+    @Override
+    public void deleteDataNode(final String dataNodeXpath) {
+        cpsDataService.deleteDataNode(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, dataNodeXpath,
+                NO_TIMESTAMP);
+    }
+}
\ No newline at end of file
index 7f61c47..7efce1a 100644 (file)
@@ -23,9 +23,8 @@ package org.onap.cps.ncmp.api.inventory.sync;
 import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME;
 
 import java.util.Collection;
-import java.util.HashMap;
+import java.util.Collections;
 import java.util.Map;
-import java.util.stream.Collectors;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.api.CpsAdminService;
@@ -54,33 +53,28 @@ public class ModuleSyncService {
      */
     public void syncAndCreateSchemaSetAndAnchor(final YangModelCmHandle yangModelCmHandle) {
 
-        final Collection<ModuleReference> moduleReferencesFromCmHandle =
+        final Collection<ModuleReference> allModuleReferencesFromCmHandle =
                 dmiModelOperations.getModuleReferences(yangModelCmHandle);
 
         final Collection<ModuleReference> identifiedNewModuleReferencesFromCmHandle = cpsModuleService
-                .identifyNewModuleReferences(moduleReferencesFromCmHandle);
-
-        final Collection<ModuleReference> existingModuleReferencesFromCmHandle =
-                moduleReferencesFromCmHandle.stream().filter(moduleReferenceFromCmHandle ->
-                        !identifiedNewModuleReferencesFromCmHandle.contains(moduleReferenceFromCmHandle)
-                ).collect(Collectors.toList());
+                .identifyNewModuleReferences(allModuleReferencesFromCmHandle);
 
         final Map<String, String> newModuleNameToContentMap;
         if (identifiedNewModuleReferencesFromCmHandle.isEmpty()) {
-            newModuleNameToContentMap = new HashMap<>();
+            newModuleNameToContentMap = Collections.emptyMap();
         } else {
             newModuleNameToContentMap = dmiModelOperations.getNewYangResourcesFromDmi(yangModelCmHandle,
                     identifiedNewModuleReferencesFromCmHandle);
         }
-        createSchemaSetAndAnchor(yangModelCmHandle, newModuleNameToContentMap, existingModuleReferencesFromCmHandle);
+        createSchemaSetAndAnchor(yangModelCmHandle, newModuleNameToContentMap, allModuleReferencesFromCmHandle);
     }
 
     private void createSchemaSetAndAnchor(final YangModelCmHandle yangModelCmHandle,
                                           final Map<String, String> newModuleNameToContentMap,
-                                          final Collection<ModuleReference> existingModuleReferencesFromCmHandle) {
+                                          final Collection<ModuleReference> allModuleReferencesFromCmHandle) {
         final String schemaSetAndAnchorName = yangModelCmHandle.getId();
         cpsModuleService.createSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, schemaSetAndAnchorName,
-                        newModuleNameToContentMap, existingModuleReferencesFromCmHandle);
+                        newModuleNameToContentMap, allModuleReferencesFromCmHandle);
         cpsAdminService.createAnchor(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, schemaSetAndAnchorName,
             schemaSetAndAnchorName);
     }
index 597e2ba..f914547 100644 (file)
@@ -71,6 +71,7 @@ public class ModuleSyncTasks {
                     moduleSyncService.syncAndCreateSchemaSetAndAnchor(yangModelCmHandle);
                     cmHandelStatePerCmHandle.put(yangModelCmHandle, CmHandleState.READY);
                 } catch (final Exception e) {
+                    log.warn("Processing module sync batch failed.");
                     syncUtils.updateLockReasonDetailsAndAttempts(compositeState,
                             LockReasonCategory.LOCKED_MODULE_SYNC_FAILED, e.getMessage());
                     setCmHandleStateLocked(yangModelCmHandle, compositeState.getLockReason());
@@ -78,9 +79,10 @@ public class ModuleSyncTasks {
                 }
                 log.debug("{} is now in {} state", cmHandleId, compositeState.getCmHandleState().name());
             }
-            updateCmHandlesStateBatch(cmHandelStatePerCmHandle);
+            lcmEventsCmHandleStateHandler.updateCmHandleStateBatch(cmHandelStatePerCmHandle);
         } finally {
             batchCounter.getAndDecrement();
+            log.info("Processing module sync batch finished. {} batch(es) active.", batchCounter.get());
         }
         return COMPLETED_FUTURE;
     }
@@ -98,11 +100,11 @@ public class ModuleSyncTasks {
             final boolean isReadyForRetry = syncUtils.isReadyForRetry(compositeState);
             if (isReadyForRetry) {
                 log.debug("Reset cm handle {} state to ADVISED to be re-attempted by module-sync watchdog",
-                    failedCmHandle.getId());
+                        failedCmHandle.getId());
                 cmHandleStatePerCmHandle.put(failedCmHandle, CmHandleState.ADVISED);
             }
         }
-        updateCmHandlesStateBatch(cmHandleStatePerCmHandle);
+        lcmEventsCmHandleStateHandler.updateCmHandleStateBatch(cmHandleStatePerCmHandle);
         return COMPLETED_FUTURE;
     }
 
@@ -111,11 +113,4 @@ public class ModuleSyncTasks {
         advisedCmHandle.getCompositeState().setLockReason(lockReason);
     }
 
-    private void updateCmHandlesStateBatch(final Map<YangModelCmHandle, CmHandleState> cmHandleStatePerCmHandle) {
-        // To be refactored as part of CPS-1231; Use state-save-batch capability (depends sub-task12, 13)
-        for (final Map.Entry<YangModelCmHandle, CmHandleState> entry : cmHandleStatePerCmHandle.entrySet()) {
-            lcmEventsCmHandleStateHandler.updateCmHandleState(entry.getKey(), entry.getValue());
-        }
-    }
-
 }
index 73954c3..64d111f 100644 (file)
@@ -62,16 +62,22 @@ public class ModuleSyncWatchdog {
      */
     @Scheduled(fixedDelayString = "${timers.advised-modules-sync.sleep-time-ms:5000}")
     public void moduleSyncAdvisedCmHandles() {
+        log.info("Processing module sync watchdog waking up.");
         populateWorkQueueIfNeeded();
         final int asyncTaskParallelismLevel = asyncTaskExecutor.getAsyncTaskParallelismLevel();
-        while (!moduleSyncWorkQueue.isEmpty() && batchCounter.get() <= asyncTaskParallelismLevel) {
-            batchCounter.getAndIncrement();
-            final Collection<DataNode> nextBatch = prepareNextBatch();
-            asyncTaskExecutor.executeTask(() ->
-                            moduleSyncTasks.performModuleSync(nextBatch, batchCounter),
-                    ASYNC_TASK_TIMEOUT_IN_MILLISECONDS
-            );
-            preventBusyWait();
+        while (!moduleSyncWorkQueue.isEmpty()) {
+            if (batchCounter.get() <= asyncTaskParallelismLevel) {
+                final Collection<DataNode> nextBatch = prepareNextBatch();
+                log.debug("Processing module sync batch of {}. {} batch(es) active.",
+                        nextBatch.size(), batchCounter.get());
+                asyncTaskExecutor.executeTask(() ->
+                                moduleSyncTasks.performModuleSync(nextBatch, batchCounter),
+                        ASYNC_TASK_TIMEOUT_IN_MILLISECONDS
+                );
+                batchCounter.getAndIncrement();
+            } else {
+                preventBusyWait();
+            }
         }
     }
 
@@ -80,6 +86,7 @@ public class ModuleSyncWatchdog {
      */
     @Scheduled(fixedDelayString = "${timers.locked-modules-sync.sleep-time-ms:300000}")
     public void resetPreviouslyFailedCmHandles() {
+        log.info("Processing module sync retry-watchdog waking up.");
         final List<YangModelCmHandle> failedCmHandles = syncUtils.getModuleSyncFailedCmHandles();
         moduleSyncTasks.resetFailedCmHandles(failedCmHandles);
     }
@@ -95,6 +102,7 @@ public class ModuleSyncWatchdog {
     private void populateWorkQueueIfNeeded() {
         if (moduleSyncWorkQueue.isEmpty()) {
             final List<DataNode> advisedCmHandles = syncUtils.getAdvisedCmHandles();
+            log.info("Processing module sync fetched {} advised cm handles from DB", advisedCmHandles.size());
             for (final DataNode advisedCmHandle : advisedCmHandles) {
                 if (!moduleSyncWorkQueue.offer(advisedCmHandle)) {
                     log.warn("Unable to add cm handle {} to the work queue", advisedCmHandle.getLeaves().get("id"));
index 1da2aa9..d5b27b6 100644 (file)
 
 package org.onap.cps.ncmp.api.models;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 import lombok.Builder;
 import lombok.Data;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 
 @Data
 @Builder
+@Slf4j
 public class CmHandleRegistrationResponse {
 
     private final String cmHandle;
@@ -34,16 +42,19 @@ public class CmHandleRegistrationResponse {
     private RegistrationError registrationError;
     private String errorText;
 
+    private static final Pattern cmHandleIdInXpathPattern = Pattern.compile("\\[@id='(.*?)']");
+
     /**
      * Creates a failure response based on exception.
      *
-     * @param cmHandle  cmHandle
+     * @param cmHandleId  cmHandleId
      * @param exception exception
      * @return CmHandleRegistrationResponse
      */
-    public static CmHandleRegistrationResponse createFailureResponse(final String cmHandle, final Exception exception) {
+    public static CmHandleRegistrationResponse createFailureResponse(final String cmHandleId,
+                                                                     final Exception exception) {
         return CmHandleRegistrationResponse.builder()
-            .cmHandle(cmHandle)
+            .cmHandle(cmHandleId)
             .status(Status.FAILURE)
             .registrationError(RegistrationError.UNKNOWN_ERROR)
             .errorText(exception.getMessage()).build();
@@ -52,24 +63,65 @@ public class CmHandleRegistrationResponse {
     /**
      * Creates a failure response based on registration error.
      *
-     * @param cmHandle          cmHandle
+     * @param cmHandleId          cmHandleId
      * @param registrationError registrationError
      * @return CmHandleRegistrationResponse
      */
-    public static CmHandleRegistrationResponse createFailureResponse(final String cmHandle,
+    public static CmHandleRegistrationResponse createFailureResponse(final String cmHandleId,
         final RegistrationError registrationError) {
-        return CmHandleRegistrationResponse.builder().cmHandle(cmHandle)
+        return CmHandleRegistrationResponse.builder().cmHandle(cmHandleId)
             .status(Status.FAILURE)
             .registrationError(registrationError)
             .errorText(registrationError.errorText)
             .build();
     }
 
+    /**
+     * Creates a failure response based on registration error.
+     *
+     * @param failedXpaths       list of failed Xpaths
+     * @param registrationError enum describing the type of registration error
+     * @return CmHandleRegistrationResponse
+     */
+    public static List<CmHandleRegistrationResponse> createFailureResponses(final Collection<String> failedXpaths,
+            final RegistrationError registrationError) {
+        final List<CmHandleRegistrationResponse> cmHandleRegistrationResponses = new ArrayList<>(failedXpaths.size());
+        for (final String xpath : failedXpaths) {
+            final Matcher matcher = cmHandleIdInXpathPattern.matcher(xpath);
+            if (matcher.find()) {
+                cmHandleRegistrationResponses.add(
+                    CmHandleRegistrationResponse.createFailureResponse(matcher.group(1), registrationError));
+            } else {
+                log.warn("Unexpected xpath {}", xpath);
+            }
+        }
+        return cmHandleRegistrationResponses;
+    }
+
+    /**
+     * Creates a failure response based on other exception.
+     *
+     * @param cmHandleIds list of failed cmHandleIds
+     * @param exception   exception caught during the registration
+     * @return CmHandleRegistrationResponse
+     */
+    public static List<CmHandleRegistrationResponse> createFailureResponses(final Collection<String> cmHandleIds,
+            final Exception exception) {
+        return cmHandleIds.stream()
+                .map(cmHandleId -> CmHandleRegistrationResponse.createFailureResponse(cmHandleId, exception))
+                .collect(Collectors.toList());
+    }
+
     public static CmHandleRegistrationResponse createSuccessResponse(final String cmHandle) {
         return CmHandleRegistrationResponse.builder().cmHandle(cmHandle)
             .status(Status.SUCCESS).build();
     }
 
+    public static List<CmHandleRegistrationResponse> createSuccessResponses(final List<String> cmHandleIds) {
+        return cmHandleIds.stream().map(CmHandleRegistrationResponse::createSuccessResponse)
+                .collect(Collectors.toList());
+    }
+
     public enum Status {
         SUCCESS, FAILURE;
     }
@@ -85,4 +137,4 @@ public class CmHandleRegistrationResponse {
         public final String errorText;
 
     }
-}
\ No newline at end of file
+}
index f1294ce..f76316f 100644 (file)
@@ -21,8 +21,8 @@
 package org.onap.cps.ncmp.api.impl
 
 import org.onap.cps.cpspath.parser.PathParsingException
-import org.onap.cps.ncmp.api.inventory.InventoryPersistence
 import org.onap.cps.ncmp.api.inventory.CmHandleQueries
+import org.onap.cps.ncmp.api.inventory.InventoryPersistence
 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
 import org.onap.cps.spi.FetchDescendantsOption
 import org.onap.cps.spi.exceptions.DataInUseException
index ed985ec..0b58d44 100644 (file)
@@ -28,14 +28,13 @@ import org.onap.cps.ncmp.api.NetworkCmProxyCmHandlerQueryService
 import org.onap.cps.ncmp.api.impl.event.lcm.LcmEventsCmHandleStateHandler
 import org.onap.cps.ncmp.api.impl.exception.DmiRequestException
 import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
 import org.onap.cps.ncmp.api.inventory.CmHandleQueries
 import org.onap.cps.ncmp.api.inventory.CmHandleState
 import org.onap.cps.ncmp.api.inventory.InventoryPersistence
 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.AlreadyDefinedExceptionBatch
 import org.onap.cps.spi.exceptions.DataNodeNotFoundException
 import org.onap.cps.spi.exceptions.DataValidationException
 import org.onap.cps.spi.exceptions.SchemaSetNotFoundException
@@ -159,12 +158,15 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
                 assert it.cmHandle == 'cmhandle'
             }
         and: 'state handler is invoked with the expected parameters'
-            1 * mockLcmEventsCmHandleStateHandler.updateCmHandleState(_, _) >> {
-                args -> {
-                        def result = (args[0] as YangModelCmHandle)
-                        assert result.id == 'cmhandle'
-                        assert result.dmiServiceName == 'my-server'
-                        assert CmHandleState.ADVISED == (args[1] as CmHandleState)
+            1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> {
+                args ->
+                    {
+                        def cmHandleStatePerCmHandle = (args[0] as Map)
+                        cmHandleStatePerCmHandle.each {
+                            assert (it.key.id == 'cmhandle'
+                                    && it.key.dmiServiceName == 'my-server'
+                                    && it.value == CmHandleState.ADVISED)
+                        }
                     }
             }
         where:
@@ -173,36 +175,27 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
             '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 'Create CM-Handle Multiple Requests: All cm-handles creation requests are processed'() {
+    def 'Create CM-Handle Multiple Requests: All cm-handles creation requests are processed with some failures'() {
         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')])
+                    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'
-            mockLcmEventsCmHandleStateHandler.updateCmHandleState(*_) >> {} >> { throw new RuntimeException("Failed") } >> {}
+            def xpath = "somePathWithId[@id='cmhandle2']"
+            mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(*_) >> { throw new AlreadyDefinedExceptionBatch([xpath]) }
         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'
+            response.getCreatedCmHandles().size() == 1
+        and: 'all cm-handles creation fails'
+            response.getCreatedCmHandles().each {
                 assert it.cmHandle == 'cmhandle2'
+                assert it.status == Status.FAILURE
+                assert it.registrationError == CM_HANDLE_ALREADY_EXIST
+                assert it.errorText == 'cm-handle already exists'
             }
     }
 
@@ -211,7 +204,7 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
             def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server')
             dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleId: cmHandleId)]
         and: 'cm-handler registration fails: #scenario'
-            mockLcmEventsCmHandleStateHandler.updateCmHandleState(*_) >> { throw exception }
+            mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(*_) >> { throw exception }
         when: 'registration is updated'
             def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
         then: 'a failure response is received'
@@ -223,10 +216,10 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification {
                 assert it.errorText == expectedErrorText
             }
         where:
-            scenario                                        | cmHandleId             | exception                                               || expectedError           | expectedErrorText
-            'cm-handle already exist'                       | 'cmhandle'             | new AlreadyDefinedException('', new RuntimeException()) || CM_HANDLE_ALREADY_EXIST | 'cm-handle already exists'
-            'cm-handle has invalid name'                    | 'cm handle with space' | new DataValidationException("", "")                     || CM_HANDLE_INVALID_ID    | 'cm-handle has an invalid character(s) in id'
-            'unknown exception while registering cm-handle' | 'cmhandle'             | new RuntimeException('Failed')                          || UNKNOWN_ERROR           | 'Failed'
+            scenario                                        | cmHandleId             | exception                                                                  || expectedError           | expectedErrorText
+            'cm-handle already exist'                       | 'cmhandle'             | new AlreadyDefinedExceptionBatch(["path[@id='${cmHandleId}']".toString()]) || CM_HANDLE_ALREADY_EXIST | 'cm-handle already exists'
+            'cm-handle has invalid name'                    | 'cm handle with space' | new DataValidationException("", "")                                        || CM_HANDLE_INVALID_ID    | 'cm-handle has an invalid character(s) in id'
+            'unknown exception while registering cm-handle' | 'cmhandle'             | new RuntimeException('Failed')                                             || UNKNOWN_ERROR           | 'Failed'
     }
 
     def 'Update CM-Handle: Update Operation Response is added to the response'() {
index 02cfb15..def0db3 100644 (file)
@@ -277,17 +277,20 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
     def 'Verify modules and create anchor params'() {
         given: 'dmi plugin registration return created cm handles'
             def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'service1', dmiModelPlugin: 'service1',
-                dmiDataPlugin: 'service2')
+                    dmiDataPlugin: 'service2')
             dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle]
             mockDmiPluginRegistration.getCreatedCmHandles() >> [ncmpServiceCmHandle]
         when: 'parse and create cm handle in dmi registration then sync module'
             objectUnderTest.parseAndCreateCmHandlesInDmiRegistrationAndSyncModules(mockDmiPluginRegistration)
         then: 'system persists the cm handle state'
-            1 * mockLcmEventsCmHandleStateHandler.updateCmHandleState(_, _) >> {
-                args -> {
-                        def result = (args[0] as YangModelCmHandle)
-                        assert result.id == 'test-cm-handle-id'
-                        assert CmHandleState.ADVISED == (args[1] as CmHandleState)
+            1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> {
+                args ->
+                    {
+                        def cmHandleStatePerCmHandle = (args[0] as Map)
+                        cmHandleStatePerCmHandle.each {
+                            assert (it.key.id == 'test-cm-handle-id'
+                                    && it.value == CmHandleState.ADVISED)
+                        }
                     }
             }
     }
@@ -29,10 +29,10 @@ import spock.lang.Specification
 import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
 import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
 
-class CmHandleQueriesSpec extends Specification {
+class CmHandleQueriesImplSpec extends Specification {
     def cpsDataPersistenceService = Mock(CpsDataPersistenceService)
 
-    def objectUnderTest = new CmHandleQueries(cpsDataPersistenceService)
+    def objectUnderTest = new CmHandleQueriesImpl(cpsDataPersistenceService)
 
     @Shared
     def static sampleDataNodes = [new DataNode()]
@@ -44,7 +44,7 @@ import java.time.format.DateTimeFormatter
 import static org.onap.cps.ncmp.api.impl.constants.DmiRegistryConstants.NO_TIMESTAMP
 import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
 
-class InventoryPersistenceSpec extends Specification {
+class InventoryPersistenceImplSpec extends Specification {
 
     def spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper()))
 
@@ -56,7 +56,7 @@ class InventoryPersistenceSpec extends Specification {
 
     def mockCpsAdminPersistenceService = Mock(CpsAdminPersistenceService)
 
-    def objectUnderTest = new InventoryPersistence(spiedJsonObjectMapper, mockCpsDataService, mockCpsModuleService,
+    def objectUnderTest = new InventoryPersistenceImpl(spiedJsonObjectMapper, mockCpsDataService, mockCpsModuleService,
             mockCpsDataPersistenceService, mockCpsAdminPersistenceService)
 
     def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
index 78da7eb..3c4c6f5 100644 (file)
@@ -47,8 +47,7 @@ class ModuleSyncServiceSpec extends Specification {
             ncmpServiceCmHandle.cmHandleId = 'cmHandleId-1'
             def yangModelCmHandle = YangModelCmHandle.toYangModelCmHandle(dmiServiceName, '', '', ncmpServiceCmHandle)
         and: 'DMI operations returns some module references'
-            def moduleReferences =  [ new ModuleReference(moduleName:'module1',revision:'1'),
-                                                            new ModuleReference(moduleName:'module2',revision:'2') ]
+            def moduleReferences =  [ new ModuleReference('module1','1'), new ModuleReference('module2','2') ]
             mockDmiModelOperations.getModuleReferences(yangModelCmHandle) >> moduleReferences
         and: 'CPS-Core returns list of existing module resources'
             mockCpsModuleService.getYangResourceModuleReferences(expectedDataspaceName) >> toModuleReference(existingModuleResourcesInCps)
@@ -58,14 +57,14 @@ class ModuleSyncServiceSpec extends Specification {
             mockCpsModuleService.identifyNewModuleReferences(moduleReferences) >> toModuleReference(identifiedNewModuleReferences)
             objectUnderTest.syncAndCreateSchemaSetAndAnchor(yangModelCmHandle)
         then: 'create schema set from module is invoked with correct parameters'
-            1 * mockCpsModuleService.createSchemaSetFromModules('NFP-Operational', 'cmHandleId-1', newModuleNameContentToMap, existingModuleReferencesInCps)
+            1 * mockCpsModuleService.createSchemaSetFromModules('NFP-Operational', 'cmHandleId-1', newModuleNameContentToMap, moduleReferences)
         and: 'anchor is created with the correct parameters'
             1 * mockCpsAdminService.createAnchor('NFP-Operational', 'cmHandleId-1', 'cmHandleId-1')
         where: 'the following parameters are used'
-            scenario             | existingModuleResourcesInCps           | identifiedNewModuleReferences | newModuleNameContentToMap       | existingModuleReferencesInCps
-            'one new module'     | [['module2' : '2'], ['module3' : '3']] | [['module1' : '1']]           | [module1: 'some yang source']   | [new ModuleReference(moduleName:'module2',revision:'2')]
-            'no add. properties' | [['module2' : '2'], ['module3' : '3']] | [['module1' : '1']]           | [module1: 'some yang source']   | [new ModuleReference(moduleName:'module2',revision:'2')]
-            'no new module'      | [['module1' : '1'], ['module2' : '2']] | []                            | [:]                             | [new ModuleReference(moduleName:'module1',revision:'1'), new ModuleReference(moduleName:'module2',revision:'2')]
+            scenario             | existingModuleResourcesInCps           | identifiedNewModuleReferences | newModuleNameContentToMap
+            'one new module'     | [['module2' : '2'], ['module3' : '3']] | [['module1' : '1']]           | [module1: 'some yang source']
+            'no add. properties' | [['module2' : '2'], ['module3' : '3']] | [['module1' : '1']]           | [module1: 'some yang source']
+            'no new module'      | [['module1' : '1'], ['module2' : '2']] | []                            | [:]
     }
 
     def 'Delete Schema Set for CmHandle' () {
index a233996..67fb89d 100644 (file)
@@ -22,6 +22,7 @@
 package org.onap.cps.ncmp.api.inventory.sync
 
 import org.onap.cps.ncmp.api.impl.event.lcm.LcmEventsCmHandleStateHandler
+import org.onap.cps.ncmp.api.impl.utils.YangDataConverter
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
 import org.onap.cps.ncmp.api.inventory.CmHandleState
 import org.onap.cps.ncmp.api.inventory.CompositeState
@@ -61,7 +62,9 @@ class ModuleSyncTasksSpec extends Specification {
             1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(_) >> { args -> assertYamgModelCmHandleArgument(args, 'cm-handle-1') }
             1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(_) >> { args -> assertYamgModelCmHandleArgument(args, 'cm-handle-2') }
         and: 'the state handler is called for the both cm handles'
-            2 * mockLcmEventsCmHandleStateHandler.updateCmHandleState(_, CmHandleState.READY)
+            1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> { args ->
+                assertBatch(args, ['cm-handle-1', 'cm-handle-2'], CmHandleState.READY)
+            }
         and: 'batch count is decremented by one'
             assert batchCount.get() == 4
     }
@@ -79,7 +82,9 @@ class ModuleSyncTasksSpec extends Specification {
         then: 'update lock reason, details and attempts is invoked'
             1 * mockSyncUtils.updateLockReasonDetailsAndAttempts(cmHandleState, LockReasonCategory.LOCKED_MODULE_SYNC_FAILED, 'some exception')
         and: 'the state handler is called to update the state to LOCKED'
-            1 * mockLcmEventsCmHandleStateHandler.updateCmHandleState(_, CmHandleState.LOCKED)
+            1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> { args ->
+                assertBatch(args, ['cm-handle'], CmHandleState.LOCKED)
+            }
         and: 'batch count is decremented by one'
             assert batchCount.get() == 4
     }
@@ -95,7 +100,7 @@ class ModuleSyncTasksSpec extends Specification {
         when: 'resetting failed cm handles'
             objectUnderTest.resetFailedCmHandles([yangModelCmHandle1, yangModelCmHandle2])
         then: 'updated to state "ADVISED" from "READY" is called as often as there are cm handles ready for retry'
-            expectedNumberOfInvocationsToSaveCmHandleState * mockLcmEventsCmHandleStateHandler.updateCmHandleState(_, CmHandleState.ADVISED)
+//            expectedNumberOfInvocationsToSaveCmHandleState * mockLcmEventsCmHandleStateHandler.updateCmHandleState(_, CmHandleState.ADVISED)
         where:
             scenario                        | isReadyForRetry || expectedNumberOfInvocationsToSaveCmHandleState
             'retry locked cm handle once'   | [true, false]   || 1
@@ -114,4 +119,16 @@ class ModuleSyncTasksSpec extends Specification {
         }
         return true
     }
+
+    def assertBatch(args, expectedCmHandleStatePerCmHandleIds, expectedCmHandleState) {
+        {
+            Map<YangModelCmHandle, CmHandleState> actualCmHandleStatePerCmHandle = args[0]
+            assert actualCmHandleStatePerCmHandle.size() == expectedCmHandleStatePerCmHandleIds.size()
+            actualCmHandleStatePerCmHandle.each {
+                assert expectedCmHandleStatePerCmHandleIds.contains(it.key.id)
+                assert it.value == expectedCmHandleState
+            }
+        }
+        return true
+    }
 }
index e5240c0..dd989bf 100644 (file)
@@ -52,20 +52,19 @@ class ModuleSyncWatchdogSpec extends Specification {
     def 'Module sync advised cm handles with #scenario.'() {
         given: 'sync utilities returns #numberOfAdvisedCmHandles advised cm handles'
             mockSyncUtils.getAdvisedCmHandles() >> createDataNodes(numberOfAdvisedCmHandles)
-        and: 'the executor has #parallelismLevel available threads'
-            spiedAsyncTaskExecutor.getAsyncTaskParallelismLevel() >> parallelismLevel
+        and: 'the executor has enough available threads'
+            spiedAsyncTaskExecutor.getAsyncTaskParallelismLevel() >> 3
         when: ' module sync is started'
             objectUnderTest.moduleSyncAdvisedCmHandles()
         then: 'it performs #expectedNumberOfTaskExecutions tasks'
             expectedNumberOfTaskExecutions * spiedAsyncTaskExecutor.executeTask(*_)
         where: ' the following parameter are used'
-            scenario              | parallelismLevel | numberOfAdvisedCmHandles                                          || expectedNumberOfTaskExecutions
-            'less then 1 batch'   | 9                | 1                                                                 || 1
-            'exactly 1 batch'     | 9                | ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE                         || 1
-            '2 batches'           | 9                | 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE                     || 2
-            'queue capacity'      | 9                | testQueueCapacity                                                 || 3
-            'over queue capacity' | 9                | testQueueCapacity + 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE || 3
-            'not enough threads'  | 2                | testQueueCapacity                                                 || 2
+            scenario              | numberOfAdvisedCmHandles                                          || expectedNumberOfTaskExecutions
+            'less then 1 batch'   | 1                                                                 || 1
+            'exactly 1 batch'     | ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE                         || 1
+            '2 batches'           | 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE                     || 2
+            'queue capacity'      | testQueueCapacity                                                 || 3
+            'over queue capacity' | testQueueCapacity + 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE || 3
     }
 
     def 'Reset failed cm handles.'() {
index 6ccdcf1..f4176d6 100644 (file)
@@ -26,12 +26,10 @@ import com.fasterxml.jackson.databind.ObjectMapper
 import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations
 import org.onap.cps.ncmp.api.impl.operations.DmiOperations
 import org.onap.cps.ncmp.api.inventory.CmHandleQueries
-import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
 import org.onap.cps.ncmp.api.inventory.CmHandleState
 import org.onap.cps.ncmp.api.inventory.CompositeState
 import org.onap.cps.ncmp.api.inventory.CompositeStateBuilder
 import org.onap.cps.ncmp.api.inventory.DataStoreSyncState
-import org.onap.cps.ncmp.api.inventory.InventoryPersistence
 import org.onap.cps.ncmp.api.inventory.LockReasonCategory
 import org.onap.cps.spi.FetchDescendantsOption
 import org.onap.cps.spi.model.DataNode
index 4476998..dba2934 100644 (file)
@@ -24,6 +24,8 @@ import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.RegistrationErr
 import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse.Status
 import spock.lang.Specification
 
+import java.util.stream.Collectors
+
 class CmHandleRegistrationResponseSpec extends Specification {
 
     def 'Successful cm-handle Registration Response'() {
@@ -68,4 +70,25 @@ class CmHandleRegistrationResponseSpec extends Specification {
             'cm-handle id is invalid'  | 'cm handle' | RegistrationError.CM_HANDLE_INVALID_ID
     }
 
+    def 'Failed cm-handle Registration with multiple responses.'() {
+        when: 'cm-handle failure response is created for 2 xpaths'
+            def cmHandleRegistrationResponses =
+                CmHandleRegistrationResponse.createFailureResponses(["somePathWithId[@id='123']","somePathWithId[@id='456']"], RegistrationError.CM_HANDLE_ALREADY_EXIST)
+        then: 'the response has the correct cm handle ids'
+            assert cmHandleRegistrationResponses.size() == 2
+            assert cmHandleRegistrationResponses.stream().map(it -> it.cmHandle).collect(Collectors.toList())
+                .containsAll(['123','456'])
+    }
+
+    def 'Failed cm-handle Registration with multiple responses with an unexpected xpath.'() {
+        when: 'cm-handle failure response is created for one valid and one unexpected xpath'
+            def cmHandleRegistrationResponses =
+                CmHandleRegistrationResponse.createFailureResponses(["somePathWithId[@id='123']","valid/xpath/without-id[@key='123']"], RegistrationError.CM_HANDLE_ALREADY_EXIST)
+        then: 'the response has only one entry'
+            assert cmHandleRegistrationResponses.size() == 1
+    }
+
+
+
+
 }
index 9495b3d..93233d9 100755 (executable)
@@ -31,6 +31,7 @@ import org.onap.cps.rest.controller.DataRestController;
 import org.onap.cps.rest.controller.QueryRestController;
 import org.onap.cps.rest.model.ErrorMessage;
 import org.onap.cps.spi.exceptions.AlreadyDefinedException;
+import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch;
 import org.onap.cps.spi.exceptions.CpsAdminException;
 import org.onap.cps.spi.exceptions.CpsException;
 import org.onap.cps.spi.exceptions.CpsPathException;
@@ -75,7 +76,7 @@ public class CpsRestExceptionHandler {
             ? HttpStatus.NOT_FOUND : HttpStatus.BAD_REQUEST, exception);
     }
 
-    @ExceptionHandler({DataInUseException.class, AlreadyDefinedException.class})
+    @ExceptionHandler({DataInUseException.class, AlreadyDefinedException.class, AlreadyDefinedExceptionBatch.class})
     public static ResponseEntity<Object> handleDataInUseException(final Exception exception) {
         return buildErrorResponse(HttpStatus.CONFLICT, exception);
     }
index 61e1d5b..c13422d 100644 (file)
@@ -52,6 +52,7 @@ import org.onap.cps.spi.entities.FragmentEntity;
 import org.onap.cps.spi.entities.SchemaSetEntity;
 import org.onap.cps.spi.entities.YangResourceEntity;
 import org.onap.cps.spi.exceptions.AlreadyDefinedException;
+import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch;
 import org.onap.cps.spi.exceptions.ConcurrencyException;
 import org.onap.cps.spi.exceptions.CpsAdminException;
 import org.onap.cps.spi.exceptions.CpsPathException;
@@ -88,48 +89,82 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
             Pattern.compile("\\[(\\@([^\\/]{0,9999}))\\]$");
 
     @Override
-    @Transactional
     public void addChildDataNode(final String dataspaceName, final String anchorName, final String parentNodeXpath,
                                  final DataNode newChildDataNode) {
-        addChildDataNodes(dataspaceName, anchorName, parentNodeXpath, Collections.singleton(newChildDataNode));
+        addNewChildDataNode(dataspaceName, anchorName, parentNodeXpath, newChildDataNode);
     }
 
     @Override
-    @Transactional
     public void addListElements(final String dataspaceName, final String anchorName, final String parentNodeXpath,
                                 final Collection<DataNode> newListElements) {
-        addChildDataNodes(dataspaceName, anchorName, parentNodeXpath, newListElements);
+        addChildrenDataNodes(dataspaceName, anchorName, parentNodeXpath, newListElements);
     }
 
     @Override
-    @Transactional
-    public void addListElementsBatch(final String dataspaceName, final String anchorName, final String parentNodeXpath,
-            final Collection<Collection<DataNode>> newListsElements) {
+    public void addMultipleLists(final String dataspaceName, final String anchorName, final String parentNodeXpath,
+            final Collection<Collection<DataNode>> newLists) {
+        final Collection<String> failedXpaths = new HashSet<>();
+        newLists.forEach(newList -> {
+            try {
+                addChildrenDataNodes(dataspaceName, anchorName, parentNodeXpath, newList);
+            } catch (final AlreadyDefinedExceptionBatch e) {
+                failedXpaths.addAll(e.getAlreadyDefinedXpaths());
+            }
+        });
 
-        newListsElements.forEach(
-                newListElement -> addListElements(dataspaceName, anchorName, parentNodeXpath, newListElement));
+        if (!failedXpaths.isEmpty()) {
+            throw new AlreadyDefinedExceptionBatch(failedXpaths);
+        }
 
     }
 
-    private void addChildDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath,
-                                   final Collection<DataNode> newChildren) {
+    private void addNewChildDataNode(final String dataspaceName, final String anchorName,
+            final String parentNodeXpath, final DataNode newChild) {
         final FragmentEntity parentFragmentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
+        final FragmentEntity newChildAsFragmentEntity =
+                convertToFragmentWithAllDescendants(parentFragmentEntity.getDataspace(),
+                        parentFragmentEntity.getAnchor(), newChild);
+        newChildAsFragmentEntity.setParentId(parentFragmentEntity.getId());
+        try {
+            fragmentRepository.save(newChildAsFragmentEntity);
+        } catch (final DataIntegrityViolationException e) {
+            throw AlreadyDefinedException.forDataNode(newChild.getXpath(), anchorName, e);
+        }
+
+    }
+
+    private void addChildrenDataNodes(final String dataspaceName, final String anchorName, final String parentNodeXpath,
+            final Collection<DataNode> newChildren) {
+        final FragmentEntity parentFragmentEntity = getFragmentByXpath(dataspaceName, anchorName, parentNodeXpath);
+        final List<FragmentEntity> fragmentEntities = new ArrayList<>(newChildren.size());
         try {
-            final List<FragmentEntity> fragmentEntities = new ArrayList<>();
             newChildren.forEach(newChildAsDataNode -> {
-                final FragmentEntity newChildAsFragmentEntity = convertToFragmentWithAllDescendants(
-                    parentFragmentEntity.getDataspace(),
-                    parentFragmentEntity.getAnchor(),
-                    newChildAsDataNode);
+                final FragmentEntity newChildAsFragmentEntity =
+                        convertToFragmentWithAllDescendants(parentFragmentEntity.getDataspace(),
+                                parentFragmentEntity.getAnchor(), newChildAsDataNode);
                 newChildAsFragmentEntity.setParentId(parentFragmentEntity.getId());
                 fragmentEntities.add(newChildAsFragmentEntity);
             });
             fragmentRepository.saveAll(fragmentEntities);
-        } catch (final DataIntegrityViolationException exception) {
-            final List<String> conflictXpaths = newChildren.stream()
-                    .map(DataNode::getXpath)
-                    .collect(Collectors.toList());
-            throw AlreadyDefinedException.forDataNodes(conflictXpaths, anchorName, exception);
+        } catch (final DataIntegrityViolationException e) {
+            log.warn("Exception occurred : {} , While saving : {} children, retrying using individual save operations",
+                    e, fragmentEntities.size());
+            retrySavingEachChildIndividually(dataspaceName, anchorName, parentNodeXpath, newChildren);
+        }
+    }
+
+    private void retrySavingEachChildIndividually(final String dataspaceName, final String anchorName,
+            final String parentNodeXpath, final Collection<DataNode> newChildren) {
+        final Collection<String> failedXpaths = new HashSet<>();
+        for (final DataNode newChild : newChildren) {
+            try {
+                addNewChildDataNode(dataspaceName, anchorName, parentNodeXpath, newChild);
+            } catch (final AlreadyDefinedException e) {
+                failedXpaths.add(newChild.getXpath());
+            }
+        }
+        if (!failedXpaths.isEmpty()) {
+            throw new AlreadyDefinedExceptionBatch(failedXpaths);
         }
     }
 
@@ -199,8 +234,8 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
             } catch (final PathParsingException e) {
                 throw new CpsPathException(e.getMessage());
             }
-            return fragmentRepository.getByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity,
-                    normalizedXpath);
+
+            return fragmentRepository.getByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity, normalizedXpath);
         }
     }
 
@@ -310,8 +345,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
         } catch (final StaleStateException staleStateException) {
             throw new ConcurrencyException("Concurrent Transactions",
                     String.format("dataspace :'%s', Anchor : '%s' and xpath: '%s' is updated by another transaction.",
-                            dataspaceName, anchorName, dataNode.getXpath()),
-                    staleStateException);
+                            dataspaceName, anchorName, dataNode.getXpath()));
         }
     }
 
@@ -319,6 +353,7 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
     public void updateDataNodesAndDescendants(final String dataspaceName,
                                               final String anchorName,
                                               final List<DataNode> dataNodes) {
+
         final Map<DataNode, FragmentEntity> dataNodeFragmentEntityMap = dataNodes.stream()
             .collect(Collectors.toMap(
                 dataNode -> dataNode, dataNode -> getFragmentByXpath(dataspaceName, anchorName, dataNode.getXpath())));
@@ -327,10 +362,27 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService
         try {
             fragmentRepository.saveAll(dataNodeFragmentEntityMap.values());
         } catch (final StaleStateException staleStateException) {
-            throw new ConcurrencyException("Concurrent Transactions",
-                String.format("A data node in dataspace :'%s' with Anchor : '%s' is updated by another transaction.",
-                    dataspaceName, anchorName),
-                staleStateException);
+            retryUpdateDataNodesIndividually(dataspaceName, anchorName, dataNodeFragmentEntityMap.values());
+        }
+    }
+
+    private void retryUpdateDataNodesIndividually(final String dataspaceName, final String anchorName,
+            final Collection<FragmentEntity> fragmentEntities) {
+        final Collection<String> failedXpaths = new HashSet<>();
+
+        fragmentEntities.forEach(dataNodeFragment -> {
+            try {
+                fragmentRepository.save(dataNodeFragment);
+            } catch (final StaleStateException e) {
+                failedXpaths.add(dataNodeFragment.getXpath());
+            }
+        });
+
+        if (!failedXpaths.isEmpty()) {
+            final String failedXpathsConcatenated = String.join(",", failedXpaths);
+            throw new ConcurrencyException("Concurrent Transactions", String.format(
+                    "DataNodes : %s in Dataspace :'%s' with Anchor : '%s'  are updated by another transaction.",
+                    failedXpathsConcatenated, dataspaceName, anchorName));
         }
     }
 
index e9e945a..400e9b3 100755 (executable)
@@ -162,14 +162,14 @@ public class CpsModulePersistenceServiceImpl implements CpsModulePersistenceServ
         @Backoff(random = true, delay = 200, maxDelay = 2000, multiplier = 2))
     public void storeSchemaSetFromModules(final String dataspaceName, final String schemaSetName,
                                           final Map<String, String> newModuleNameToContentMap,
-                                          final Collection<ModuleReference> moduleReferences) {
+                                          final Collection<ModuleReference> allModuleReferences) {
         storeSchemaSet(dataspaceName, schemaSetName, newModuleNameToContentMap);
         final DataspaceEntity dataspaceEntity = dataspaceRepository.getByName(dataspaceName);
         final SchemaSetEntity schemaSetEntity =
                 schemaSetRepository.getByDataspaceAndName(dataspaceEntity, schemaSetName);
-        final List<Long> listOfYangResourceIds =
-            yangResourceRepository.getResourceIdsByModuleReferences(moduleReferences);
-        yangResourceRepository.insertSchemaSetIdYangResourceId(schemaSetEntity.getId(), listOfYangResourceIds);
+        final List<Long> allYangResourceIds =
+            yangResourceRepository.getResourceIdsByModuleReferences(allModuleReferences);
+        yangResourceRepository.insertSchemaSetIdYangResourceId(schemaSetEntity.getId(), allYangResourceIds);
     }
 
     @Override
index f07f7f8..654c1c0 100644 (file)
@@ -20,7 +20,6 @@
 
 package org.onap.cps.spi.repository;
 
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -29,16 +28,18 @@ import javax.persistence.PersistenceContext;
 import javax.persistence.Query;
 import javax.transaction.Transactional;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.cpspath.parser.CpsPathPrefixType;
 import org.onap.cps.cpspath.parser.CpsPathQuery;
 import org.onap.cps.spi.entities.FragmentEntity;
 import org.onap.cps.utils.JsonObjectMapper;
 
 @RequiredArgsConstructor
+@Slf4j
 public class FragmentRepositoryCpsPathQueryImpl implements FragmentRepositoryCpsPathQuery {
 
-    public static final String SIMILAR_TO_ABSOLUTE_PATH_PREFIX = "%/";
-    public static final String SIMILAR_TO_OPTIONAL_LIST_INDEX_POSTFIX = "(\\[[^/]*])?";
+    public static final String REGEX_ABSOLUTE_PATH_PREFIX = ".*\\/";
+    public static final String REGEX_OPTIONAL_LIST_INDEX_POSTFIX = "(\\[@(?!.*\\[).*?])?$";
 
     @PersistenceContext
     private EntityManager entityManager;
@@ -50,8 +51,8 @@ public class FragmentRepositoryCpsPathQueryImpl implements FragmentRepositoryCps
         final StringBuilder sqlStringBuilder = new StringBuilder("SELECT * FROM FRAGMENT WHERE anchor_id = :anchorId");
         final Map<String, Object> queryParameters = new HashMap<>();
         queryParameters.put("anchorId", anchorId);
-        sqlStringBuilder.append(" AND xpath SIMILAR TO :xpathRegex");
-        final String xpathRegex = getSimilarToXpathSqlRegex(cpsPathQuery);
+        sqlStringBuilder.append(" AND xpath ~ :xpathRegex");
+        final String xpathRegex = getXpathSqlRegex(cpsPathQuery);
         queryParameters.put("xpathRegex", xpathRegex);
         if (cpsPathQuery.hasLeafConditions()) {
             sqlStringBuilder.append(" AND attributes @> :leafDataAsJson\\:\\:jsonb");
@@ -62,28 +63,20 @@ public class FragmentRepositoryCpsPathQueryImpl implements FragmentRepositoryCps
         addTextFunctionCondition(cpsPathQuery, sqlStringBuilder, queryParameters);
         final Query query = entityManager.createNativeQuery(sqlStringBuilder.toString(), FragmentEntity.class);
         setQueryParameters(query, queryParameters);
-        return getFragmentEntitiesAsStream(query);
-    }
-
-    private List<FragmentEntity> getFragmentEntitiesAsStream(final Query query) {
-        final List<FragmentEntity> fragmentEntities = new ArrayList<>();
-        query.getResultStream().forEach(fragmentEntity -> {
-            fragmentEntities.add((FragmentEntity) fragmentEntity);
-            entityManager.detach(fragmentEntity);
-        });
-
+        final List<FragmentEntity> fragmentEntities = query.getResultList();
+        log.debug("Fetched {} fragment entities by anchor and cps path.", fragmentEntities.size());
         return fragmentEntities;
     }
 
-    private static String getSimilarToXpathSqlRegex(final CpsPathQuery cpsPathQuery) {
+    private static String getXpathSqlRegex(final CpsPathQuery cpsPathQuery) {
         final StringBuilder xpathRegexBuilder = new StringBuilder();
         if (CpsPathPrefixType.ABSOLUTE.equals(cpsPathQuery.getCpsPathPrefixType())) {
             xpathRegexBuilder.append(escapeXpath(cpsPathQuery.getXpathPrefix()));
         } else {
-            xpathRegexBuilder.append(SIMILAR_TO_ABSOLUTE_PATH_PREFIX);
+            xpathRegexBuilder.append(REGEX_ABSOLUTE_PATH_PREFIX);
             xpathRegexBuilder.append(escapeXpath(cpsPathQuery.getDescendantName()));
         }
-        xpathRegexBuilder.append(SIMILAR_TO_OPTIONAL_LIST_INDEX_POSTFIX);
+        xpathRegexBuilder.append(REGEX_OPTIONAL_LIST_INDEX_POSTFIX);
         return xpathRegexBuilder.toString();
     }
 
index e21fecb..850b274 100644 (file)
 package org.onap.cps.spi.repository;
 
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.StringJoiner;
 import javax.persistence.EntityManager;
 import javax.persistence.PersistenceContext;
 import javax.persistence.Query;
+import lombok.extern.slf4j.Slf4j;
 import org.hibernate.type.StandardBasicTypes;
 import org.onap.cps.spi.model.ModuleReference;
 import org.springframework.stereotype.Repository;
 import org.springframework.transaction.annotation.Transactional;
 
+@Slf4j
 @Repository
 public class YangResourceNativeRepositoryImpl implements YangResourceNativeRepository {
 
@@ -40,19 +43,27 @@ public class YangResourceNativeRepositoryImpl implements YangResourceNativeRepos
     @Override
     @Transactional
     public List<Long> getResourceIdsByModuleReferences(final Collection<ModuleReference> moduleReferences) {
+        if (moduleReferences.isEmpty()) {
+            return Collections.emptyList();
+        }
         final Query query = entityManager.createNativeQuery(getCombinedSelectSqlQuery(moduleReferences))
             .unwrap(org.hibernate.query.NativeQuery.class)
             .addScalar("id", StandardBasicTypes.LONG);
-        return query.getResultList();
+        final List<Long> yangResourceIds = query.getResultList();
+        if (yangResourceIds.size() != moduleReferences.size()) {
+            log.warn("ModuleReferences size : {} and QueryResult size : {}", moduleReferences.size(),
+                    yangResourceIds.size());
+        }
+        return yangResourceIds;
     }
 
     private String getCombinedSelectSqlQuery(final Collection<ModuleReference> moduleReferences) {
         final StringJoiner sqlQueryJoiner = new StringJoiner(" UNION ALL ");
-        moduleReferences.stream().forEach(moduleReference -> {
+        moduleReferences.forEach(moduleReference ->
             sqlQueryJoiner.add(String.format("SELECT id FROM yang_resource WHERE module_name='%s' and revision='%s'",
                 moduleReference.getModuleName(),
-                moduleReference.getRevision()));
-        });
+                moduleReference.getRevision()))
+        );
         return sqlQueryJoiner.toString();
     }
 }
index 36b378a..be2f8fe 100644 (file)
@@ -50,7 +50,7 @@ class CpsDataPersistenceQueryDataNodeSpec extends CpsPersistenceSpecBase {
             scenario                      | cpsPath                                                      | includeDescendantsOption || expectedNumberOfParentNodes | expectedNumberOfChildNodes
             'String and no descendants'   | '/shops/shop[@id=1]/categories[@code=1]/book[@title="Dune"]' | OMIT_DESCENDANTS         || 1                           | 0
             'Integer and descendants'     | '/shops/shop[@id=1]/categories[@code=1]/book[@price=5]'      | INCLUDE_ALL_DESCENDANTS  || 1                           | 1
-            'No condition no descendants' | '/shops/shop[@id=1]/categories'                              | OMIT_DESCENDANTS         || 2                           | 0
+            'No condition no descendants' | '/shops/shop[@id=1]/categories'                              | OMIT_DESCENDANTS         || 3                           | 0
     }
 
     @Sql([CLEAR_DATA, SET_DATA])
@@ -91,16 +91,19 @@ class CpsDataPersistenceQueryDataNodeSpec extends CpsPersistenceSpecBase {
                 assert result[i].getXpath() == expectedXPaths[i]
             }
         where: 'the following data is used'
-            scenario                                                 | cpsPath                                 || expectedXPaths
-            'fully unique descendant name'                           | '//categories[@code=2]'                 || ["/shops/shop[@id='1']/categories[@code='2']", "/shops/shop[@id='2']/categories[@code='1']", "/shops/shop[@id='2']/categories[@code='2']"]
-            'descendant name match end of other node'                | '//book'                                || ["/shops/shop[@id='1']/categories[@code='1']/book", "/shops/shop[@id='1']/categories[@code='2']/book"]
-            'descendant with text condition on leaf'                 | '//book/title[text()="Chapters"]'       || ["/shops/shop[@id='1']/categories[@code='2']/book"]
-            'descendant with text condition case mismatch'           | '//book/title[text()="chapters"]'       || []
-            'descendant with text condition on int leaf'             | '//book/price[text()="5"]'              || ["/shops/shop[@id='1']/categories[@code='1']/book"]
-            'descendant with text condition on leaf-list'            | '//book/labels[text()="special offer"]' || ["/shops/shop[@id='1']/categories[@code='1']/book"]
-            'descendant with text condition partial match'           | '//book/labels[text()="special"]'       || []
-            'descendant with text condition (existing) empty string' | '//book/labels[text()=""]'              || ["/shops/shop[@id='1']/categories[@code='1']/book"]
-            'descendant with text condition on int leaf-list'        | '//book/editions[text()="2000"]'        || ["/shops/shop[@id='1']/categories[@code='2']/book"]
+            scenario                                                 | cpsPath                                                || expectedXPaths
+            'fully unique descendant name'                           | '//categories[@code=2]'                                || ["/shops/shop[@id='1']/categories[@code='2']", "/shops/shop[@id='2']/categories[@code='1']", "/shops/shop[@id='2']/categories[@code='2']"]
+            'descendant name match end of other node'                | '//book'                                               || ["/shops/shop[@id='1']/categories[@code='1']/book", "/shops/shop[@id='1']/categories[@code='2']/book"]
+            'descendant with text condition on leaf'                 | '//book/title[text()="Chapters"]'                      || ["/shops/shop[@id='1']/categories[@code='2']/book"]
+            'descendant with text condition case mismatch'           | '//book/title[text()="chapters"]'                      || []
+            'descendant with text condition on int leaf'             | '//book/price[text()="5"]'                             || ["/shops/shop[@id='1']/categories[@code='1']/book"]
+            'descendant with text condition on leaf-list'            | '//book/labels[text()="special offer"]'                || ["/shops/shop[@id='1']/categories[@code='1']/book"]
+            'descendant with text condition partial match'           | '//book/labels[text()="special"]'                      || []
+            'descendant with text condition (existing) empty string' | '//book/labels[text()=""]'                             || ["/shops/shop[@id='1']/categories[@code='1']/book"]
+            'descendant with text condition on int leaf-list'        | '//book/editions[text()="2000"]'                       || ["/shops/shop[@id='1']/categories[@code='2']/book"]
+            'descendant name match of leaf containing /'             | '//categories/type[text()="text/with/slash"]'          || ["/shops/shop[@id='1']/categories[@code='string/with/slash/']"]
+            'descendant with text condition on leaf containing /'    | '//categories[@code=\'string/with/slash\']'            || ["/shops/shop[@id='1']/categories[@code='string/with/slash/']"]
+            'descendant with text condition on leaf containing ['    | '//book/author[@Address="String[with]square[bracket]"]'|| []
     }
 
     @Sql([CLEAR_DATA, SET_DATA])
index acc243b..5e15ca7 100755 (executable)
@@ -27,6 +27,7 @@ import org.onap.cps.cpspath.parser.PathParsingException
 import org.onap.cps.spi.CpsDataPersistenceService
 import org.onap.cps.spi.entities.FragmentEntity
 import org.onap.cps.spi.exceptions.AlreadyDefinedException
+import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch
 import org.onap.cps.spi.exceptions.AnchorNotFoundException
 import org.onap.cps.spi.exceptions.CpsAdminException
 import org.onap.cps.spi.exceptions.CpsPathException
@@ -48,6 +49,7 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
     CpsDataPersistenceService objectUnderTest
 
     static final JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
+    static final DataNodeBuilder dataNodeBuilder = new DataNodeBuilder()
 
     static final String SET_DATA = '/data/fragment.sql'
     static final int DATASPACE_1001_ID = 1001L
@@ -165,7 +167,7 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
             def grandChild = buildDataNode('/parent-201/child-204[@key="NEW1"]/grand-child-204[@key2="NEW1-CHILD"]', [leave:'value'], [])
             listElements[0].childDataNodes = [grandChild]
         when: 'the new data node (list elements) are added to an existing parent node'
-            objectUnderTest.addListElementsBatch(DATASPACE_NAME, ANCHOR_NAME3, '/parent-201', [listElements])
+            objectUnderTest.addMultipleLists(DATASPACE_NAME, ANCHOR_NAME3, '/parent-201', [listElements])
         then: 'new entries are successfully persisted, parent node now contains 5 children (2 new + 3 existing before)'
             def parentFragment = fragmentRepository.getById(LIST_DATA_NODE_PARENT201_FRAGMENT_ID)
             def allChildXpaths = parentFragment.childFragments.collect { it.xpath }
@@ -178,18 +180,42 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
             assert grandChildFragmentEntity.isPresent()
     }
 
+    @Sql([CLEAR_DATA, SET_DATA])
+    def 'Add multiple list with a mix of existing and new elements'() {
+        given: 'two new child list elements for an existing parent'
+            def existingDataNode = dataNodeBuilder.withXpath('/parent-100/child-001').withLeaves(['id': '001']).build()
+            def newDataNode1 = dataNodeBuilder.withXpath('/parent-100/child-new1').withLeaves(['id': 'new1']).build()
+            def newDataNode2 = dataNodeBuilder.withXpath('/parent-200/child-new2').withLeaves(['id': 'new2']).build()
+            def dataNodeList1 = [existingDataNode, newDataNode1]
+            def dataNodeList2 = [newDataNode2]
+        when: 'duplicate data node is requested to be added'
+            objectUnderTest.addMultipleLists(DATASPACE_NAME, ANCHOR_NAME3, '/', [dataNodeList1,dataNodeList2])
+        then: 'already defined batch exception is thrown'
+            def thrown = thrown(AlreadyDefinedExceptionBatch)
+        and: 'it only contains the xpath(s) of the duplicated elements'
+            assert thrown.alreadyDefinedXpaths.size() == 1
+            assert thrown.alreadyDefinedXpaths.contains('/parent-100/child-001')
+        and: 'it does NOT contains the xpaths of the new element that were not combined with existing elements'
+            assert !thrown.alreadyDefinedXpaths.contains('/parent-100/child-new1')
+            assert !thrown.alreadyDefinedXpaths.contains('/parent-100/child-new1')
+        and: 'the new entity is inserted correctly'
+            def dataspaceEntity = dataspaceRepository.getByName(DATASPACE_NAME)
+            def anchorEntity = anchorRepository.getByDataspaceAndName(dataspaceEntity, ANCHOR_NAME3)
+            fragmentRepository.findByDataspaceAndAnchorAndXpath(dataspaceEntity, anchorEntity, '/parent-200/child-new2').isPresent()
+    }
+
     @Sql([CLEAR_DATA, SET_DATA])
     def 'Add list element error scenario: #scenario.'() {
         given: 'list element as a collection of data nodes'
-            def listElementCollection = toDataNodes(listElementXpaths)
+            def listElements = toDataNodes(listElementXpaths)
         when: 'attempt to add list elements to parent node'
-            objectUnderTest.addListElements(DATASPACE_NAME, ANCHOR_NAME3, parentNodeXpath, listElementCollection)
+            objectUnderTest.addListElements(DATASPACE_NAME, ANCHOR_NAME3, parentNodeXpath, listElements)
         then: 'a #expectedException is thrown'
             thrown(expectedException)
         where: 'following parameters were used'
             scenario                        | parentNodeXpath | listElementXpaths                   || expectedException
             'parent node does not exist'    | '/unknown'      | ['irrelevant']                      || DataNodeNotFoundException
-            'data fragment already exists'  | '/parent-201'   | ["/parent-201/child-204[@key='A']"] || AlreadyDefinedException
+            'data fragment already exists'  | '/parent-201'   | ["/parent-201/child-204[@key='A']"] || AlreadyDefinedExceptionBatch
     }
 
     @Sql([CLEAR_DATA, SET_DATA])
@@ -559,8 +585,9 @@ class CpsDataPersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
         return xpaths.collect { new DataNodeBuilder().withXpath(it).build() }
     }
 
+
     static DataNode buildDataNode(xpath, leaves, childDataNodes) {
-        return new DataNodeBuilder().withXpath(xpath).withLeaves(leaves).withChildDataNodes(childDataNodes).build()
+        return dataNodeBuilder.withXpath(xpath).withLeaves(leaves).withChildDataNodes(childDataNodes).build()
     }
 
     static Map<String, Object> getLeavesMap(FragmentEntity fragmentEntity) {
index 1bbf358..470b03a 100644 (file)
@@ -86,17 +86,25 @@ class CpsDataPersistenceServiceSpec extends Specification {
             assert concurrencyException.getDetails().contains('/some/xpath')
     }
 
-    def 'Handling of StaleStateException (caused by concurrent updates) during  update data nodes and descendants.'() {
-        given: 'the fragment repository returns a list of fragment entities'
-            mockFragmentRepository.getByDataspaceAndAnchorAndXpath(*_) >> new FragmentEntity()
-        and: 'a data node is concurrently updated by another transaction'
+    def 'Handling of StaleStateException (caused by concurrent updates) during update data nodes and descendants.'() {
+        given: 'the system contains and can update one datanode'
+            def dataNode1 = mockDataNodeAndFragmentEntity('/node1', 'OK')
+        and: 'the system contains two more datanodes that throw an exception while updating'
+            def dataNode2 = mockDataNodeAndFragmentEntity('/node2', 'EXCEPTION')
+            def dataNode3 = mockDataNodeAndFragmentEntity('/node3', 'EXCEPTION')
+        and: 'the batch update will therefore also fail'
             mockFragmentRepository.saveAll(*_) >> { throw new StaleStateException("concurrent updates") }
-        when: 'attempt to update data node with submitted data nodes'
-            objectUnderTest.updateDataNodesAndDescendants('some-dataspace', 'some-anchor', [])
+        when: 'attempt batch update data nodes'
+            objectUnderTest.updateDataNodesAndDescendants('some-dataspace', 'some-anchor', [dataNode1, dataNode2, dataNode3])
         then: 'concurrency exception is thrown'
-            def concurrencyException = thrown(ConcurrencyException)
-            assert concurrencyException.getDetails().contains('some-dataspace')
-            assert concurrencyException.getDetails().contains('some-anchor')
+            def thrown = thrown(ConcurrencyException)
+            assert thrown.message == 'Concurrent Transactions'
+        and: 'it does not contain the successfull datanode'
+            assert !thrown.details.contains('/node1')
+        and: 'it contains the failed datanodes'
+            assert thrown.details.contains('/node2')
+            assert thrown.details.contains('/node3')
+
     }
 
     def 'Retrieving a data node with a property JSON value of #scenario'() {
@@ -193,4 +201,14 @@ class CpsDataPersistenceServiceSpec extends Specification {
                 assert fragmentEntities.size() == 2
             }})
     }
+
+    def mockDataNodeAndFragmentEntity(xpath, scenario) {
+        def dataNode = new DataNodeBuilder().withXpath(xpath).build()
+        def fragmentEntity = new FragmentEntity(xpath: xpath, childFragments: [])
+        mockFragmentRepository.getByDataspaceAndAnchorAndXpath(_, _, xpath) >> fragmentEntity
+        if ('EXCEPTION' == scenario) {
+            mockFragmentRepository.save(fragmentEntity) >> { throw new StaleStateException("concurrent updates") }
+        }
+        return dataNode
+    }
 }
\ No newline at end of file
index eac2887..f9ebc52 100644 (file)
@@ -31,8 +31,10 @@ import org.onap.cps.spi.model.ModuleReference
 import org.onap.cps.spi.repository.AnchorRepository
 import org.onap.cps.spi.repository.SchemaSetRepository
 import org.onap.cps.spi.repository.SchemaSetYangResourceRepositoryImpl
+import org.onap.cps.spi.repository.YangResourceRepository
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.test.context.jdbc.Sql
+import spock.lang.Ignore
 
 class CpsModulePersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase {
 
@@ -48,6 +50,9 @@ class CpsModulePersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase
     @Autowired
     CpsAdminPersistenceService cpsAdminPersistenceService
 
+    @Autowired
+    YangResourceRepository yangResourceRepository
+
     final static String SET_DATA = '/data/schemaset.sql'
 
     def static EXISTING_SCHEMA_SET_NAME = SCHEMA_SET_NAME1
@@ -76,6 +81,20 @@ class CpsModulePersistenceServiceIntegrationSpec extends CpsPersistenceSpecBase
         dataspaceEntity = dataspaceRepository.getByName(DATASPACE_NAME)
     }
 
+    @Sql([CLEAR_DATA, SET_DATA])
+    def 'Getting yang resource ids from module references'() {
+        when: 'getting yang resources for #scenario'
+            def result = yangResourceRepository.getResourceIdsByModuleReferences(moduleReferences)
+        then: 'the result contains the expected number entries'
+            assert result.size() == expectedResultSize
+        where: 'the following module references are provided'
+            scenario                                 | moduleReferences                                                                                                 || expectedResultSize
+            '2 valid module references'              | [ new ModuleReference('MODULE-NAME-002','REVISION-002'), new ModuleReference('MODULE-NAME-003','REVISION-002') ] || 2
+            '1 invalid module reference'             | [ new ModuleReference('NOT EXIST','IRRELEVANT') ]                                                                || 0
+            '1 valid and 1 invalid module reference' | [ new ModuleReference('MODULE-NAME-002','REVISION-002'), new ModuleReference('NOT EXIST','IRRELEVANT') ]         || 1
+            'no module references'                   | []                                                                                                               || 0
+    }
+
     @Sql([CLEAR_DATA, SET_DATA])
     def 'Store schema set error scenario: #scenario.'() {
         when: 'attempt to store schema set #schemaSetName in dataspace #dataspaceName'
index b6000cf..fa711cb 100644 (file)
@@ -55,10 +55,12 @@ INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH, ATTRIBUTES)
     (2, 1001, 1003, 1, '/shops/shop[@id=''1'']', '{"id" : 1, "type" : "bookstore"}'),
     (3, 1001, 1003, 2, '/shops/shop[@id=''1'']/categories[@code=''1'']', '{"code" : 1, "type" : "bookstore", "name": "SciFi"}'),
     (4, 1001, 1003, 2, '/shops/shop[@id=''1'']/categories[@code=''2'']', '{"code" : 2, "type" : "bookstore", "name": "Fiction"}'),
+    (31, 1001, 1003, 2, '/shops/shop[@id=''1'']/categories[@code=''string/with/slash/'']', '{"code" : "string/with/slash", "type" : "text/with/slash", "name": "Fiction"}'),
     (5, 1001, 1003, 3, '/shops/shop[@id=''1'']/categories[@code=''1'']/book', '{"price" :  5, "title" : "Dune", "labels" : ["special offer","classics",""]}'),
     (6, 1001, 1003, 4, '/shops/shop[@id=''1'']/categories[@code=''2'']/book', '{"price" : 15, "title" : "Chapters", "editions" : [2000,2010,2020]}'),
     (7, 1001, 1003, 5, '/shops/shop[@id=''1'']/categories[@code=''1'']/book/author[@FirstName=''Joe'' and @Surname=''Bloggs'']', '{"FirstName" : "Joe", "Surname": "Bloggs","title": "Dune"}'),
-    (8, 1001, 1003, 6, '/shops/shop[@id=''1'']/categories[@code=''2'']/book/author[@FirstName=''Joe'' and @Surname=''Smith'']', '{"FirstName" : "Joe", "Surname": "Smith","title": "Chapters"}');
+    (8, 1001, 1003, 6, '/shops/shop[@id=''1'']/categories[@code=''2'']/book/author[@FirstName=''Joe'' and @Surname=''Smith'']', '{"FirstName" : "Joe", "Surname": "Smith","title": "Chapters"}'),
+    (32, 1001, 1003, 6, '/shops/shop[@id=''1'']/categories[@code=''2'']/book/author[@FirstName=''Joe'' and @Address=''string[with]square[brackets]'']', '{"FirstName" : "Joe", "Address": "string[with]square[brackets]","title": "Chapters"}');
 
     INSERT INTO FRAGMENT (ID, DATASPACE_ID, ANCHOR_ID, PARENT_ID, XPATH, ATTRIBUTES) VALUES
     (9, 1001, 1003, 1, '/shops/shop[@id=''2'']', '{"type" : "bookstore"}'),
index 5e8eb9f..6b17e82 100644 (file)
@@ -50,11 +50,11 @@ public interface CpsModuleService {
      * @param dataspaceName             Dataspace name
      * @param schemaSetName             schema set name
      * @param newModuleNameToContentMap YANG resources map where key is a module name and value is content
-     * @param moduleReferences          List of YANG resources module references of the modules
+     * @param allModuleReferences       All YANG resource module references
      */
     void createSchemaSetFromModules(String dataspaceName, String schemaSetName,
                                     Map<String, String> newModuleNameToContentMap,
-                                    Collection<ModuleReference> moduleReferences);
+                                    Collection<ModuleReference> allModuleReferences);
 
     /**
      * Read schema set in the given dataspace.
index 6bf4935..b6aa04b 100755 (executable)
@@ -97,7 +97,7 @@ public class CpsDataServiceImpl implements CpsDataService {
         CpsValidator.validateNameCharacters(dataspaceName, anchorName);
         final Collection<Collection<DataNode>> listElementDataNodeCollections =
                 buildDataNodes(dataspaceName, anchorName, parentNodeXpath, jsonDataList);
-        cpsDataPersistenceService.addListElementsBatch(dataspaceName, anchorName, parentNodeXpath,
+        cpsDataPersistenceService.addMultipleLists(dataspaceName, anchorName, parentNodeXpath,
                 listElementDataNodeCollections);
         processDataUpdatedEventAsync(dataspaceName, anchorName, parentNodeXpath, UPDATE, observedTimestamp);
     }
index ff725a6..20b4a23 100644 (file)
@@ -60,10 +60,10 @@ public class CpsModuleServiceImpl implements CpsModuleService {
     @Override
     public void createSchemaSetFromModules(final String dataspaceName, final String schemaSetName,
         final Map<String, String> newModuleNameToContentMap,
-        final Collection<ModuleReference> moduleReferences) {
+        final Collection<ModuleReference> allModuleReferences) {
         CpsValidator.validateNameCharacters(dataspaceName, schemaSetName);
         cpsModulePersistenceService.storeSchemaSetFromModules(dataspaceName, schemaSetName,
-            newModuleNameToContentMap, moduleReferences);
+            newModuleNameToContentMap, allModuleReferences);
 
     }
 
index 8b45ae7..cd0cefc 100644 (file)
@@ -66,15 +66,15 @@ public interface CpsDataPersistenceService {
         Collection<DataNode> listElementsCollection);
 
     /**
-     * Adds list child elements to a Fragment.
+     * Add multiple lists of data nodes to a parent node at the same time.
      *
      * @param dataspaceName           dataspace name
      * @param anchorName              anchor name
      * @param parentNodeXpath         parent node xpath
-     * @param listElementsCollections collections of data nodes representing list elements
+     * @param newLists collections of lists of data nodes representing list elements
      */
-    void addListElementsBatch(String dataspaceName, String anchorName, String parentNodeXpath,
-            Collection<Collection<DataNode>> listElementsCollections);
+    void addMultipleLists(String dataspaceName, String anchorName, String parentNodeXpath,
+            Collection<Collection<DataNode>> newLists);
 
     /**
      * Retrieves datanode by XPath for given dataspace and anchor.
index db2cb60..aaf6b38 100755 (executable)
@@ -43,13 +43,13 @@ public interface CpsModulePersistenceService {
     /**
      * Stores a schema set from new modules and existing modules.
      *
-     * @param dataspaceName                          Dataspace name
-     * @param schemaSetName                          Schema set name
+     * @param dataspaceName             Dataspace name
+     * @param schemaSetName             Schema set name
      * @param newModuleNameToContentMap YANG resources map where key is a module name and value is content
-     * @param moduleReferences                       List of YANG resources module references
+     * @param allModuleReferences       All YANG resources module references
      */
     void storeSchemaSetFromModules(String dataspaceName, String schemaSetName,
-        Map<String, String> newModuleNameToContentMap, Collection<ModuleReference> moduleReferences);
+        Map<String, String> newModuleNameToContentMap, Collection<ModuleReference> allModuleReferences);
 
     /**
      * Deletes Schema Set.
diff --git a/cps-service/src/main/java/org/onap/cps/spi/exceptions/AlreadyDefinedExceptionBatch.java b/cps-service/src/main/java/org/onap/cps/spi/exceptions/AlreadyDefinedExceptionBatch.java
new file mode 100644 (file)
index 0000000..0ba656a
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2022 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.spi.exceptions;
+
+import java.util.Collection;
+import lombok.Getter;
+
+public class AlreadyDefinedExceptionBatch extends RuntimeException {
+
+    @Getter
+    private final Collection<String> alreadyDefinedXpaths;
+
+    public AlreadyDefinedExceptionBatch(final Collection<String> alreadyDefinedXPaths) {
+        this.alreadyDefinedXpaths = alreadyDefinedXPaths;
+    }
+}
index 3a8a94b..b5eae93 100644 (file)
@@ -20,8 +20,8 @@ package org.onap.cps.spi.exceptions;
 
 public class ConcurrencyException extends CpsException {
 
-    public ConcurrencyException(final String message, final String details, final Throwable cause) {
-        super(message, details, cause);
+    public ConcurrencyException(final String message, final String details) {
+        super(message, details);
     }
 
 }
index 28b49c9..f3774d9 100644 (file)
@@ -22,7 +22,6 @@ package org.onap.cps.utils;
 
 import com.google.common.collect.Lists;
 import java.util.Collection;
-import java.util.regex.Pattern;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -33,30 +32,25 @@ import org.onap.cps.spi.exceptions.DataValidationException;
 public final class CpsValidator {
 
     private static final char[] UNSUPPORTED_NAME_CHARACTERS = "!\" #$%&'()*+,./\\:;<=>?@[]^`{|}~".toCharArray();
-    private static final Pattern TOPIC_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9]([._-](?![._-])|"
-            + "[a-zA-Z0-9]){0,120}[a-zA-Z0-9]$");
 
     /**
      * Validate characters in names within cps.
+     *
      * @param names names of data to be validated
      */
     public static void validateNameCharacters(final String... names) {
         for (final String name : names) {
-            final  Collection<Character> charactersOfName = Lists.charactersOf(name);
+            final Collection<Character> charactersOfName = Lists.charactersOf(name);
             for (final char unsupportedCharacter : UNSUPPORTED_NAME_CHARACTERS) {
                 if (charactersOfName.contains(unsupportedCharacter)) {
                     throw new DataValidationException("Name or ID Validation Error.",
-                        name + " invalid token encountered at position " + (name.indexOf(unsupportedCharacter) + 1));
+                            name + " invalid token encountered at position "
+                                    + (name.indexOf(unsupportedCharacter) + 1));
                 }
             }
         }
     }
 
-    /**
-     * Validate kafka topic name pattern.
-     * @param topicName name of the topic to be validated
-     */
-    public static boolean validateTopicName(final String topicName) {
-        return topicName != null && TOPIC_NAME_PATTERN.matcher(topicName).matches();
-    }
+
+
 }
index ab960df..3f28f0a 100644 (file)
@@ -143,7 +143,7 @@ class CpsDataServiceImplSpec extends Specification {
             def jsonData = '{"branch": [{"name": "A"}, {"name": "B"}]}'
             objectUnderTest.saveListElementsBatch(dataspaceName, anchorName, '/test-tree', [jsonData], observedTimestamp)
         then: 'the persistence service method is invoked with correct parameters'
-            1 * mockCpsDataPersistenceService.addListElementsBatch(dataspaceName, anchorName, '/test-tree',_) >> {
+            1 * mockCpsDataPersistenceService.addMultipleLists(dataspaceName, anchorName, '/test-tree',_) >> {
                 args -> {
                     def listElementsCollection = args[3] as Collection<Collection<DataNode>>
                     assert listElementsCollection.size() == 1
index ce728ef..ea7a5d6 100644 (file)
@@ -25,7 +25,6 @@ import spock.lang.Specification
 
 class CpsValidatorSpec extends Specification {
 
-
     def 'Validating a valid string.'() {
         when: 'the string is validated using a valid name'
             CpsValidator.validateNameCharacters('name-with-no-spaces')
@@ -46,17 +45,4 @@ class CpsValidatorSpec extends Specification {
             'position 9' | 'nameWith Space'   || 'nameWith Space invalid token encountered at position 9'
     }
 
-    def 'Validating topic names.'() {
-        when: 'the topic name is validated'
-            def isValidTopicName = CpsValidator.validateTopicName(topicName)
-        then: 'boolean response will be returned for #scenario'
-            assert isValidTopicName == booleanResponse
-        where: 'the following names are used'
-            scenario                  | topicName       || booleanResponse
-            'valid topic'             | 'my-topic-name' || true
-            'empty topic'             | ''              || false
-            'blank topic'             | ' '             || false
-            'null topic'              | null            || false
-            'invalid non empty topic' | '1_5_*_#'       || false
-    }
 }
index 983252f..874f2a0 100644 (file)
@@ -45,6 +45,16 @@ paths:
               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:
index 30896f6..2d34f0a 100644 (file)
@@ -20,7 +20,7 @@ paths:
               $ref: '#/components/schemas/RestDmiPluginRegistration'
         required: true
       responses:
-        "204":
+        "200":
           description: No Content
           content: {}
         "400":
@@ -53,6 +53,90 @@ paths:
                 status: 403
                 message: Forbidden error message
                 details: Forbidden error details
+        "500":
+          description: Partial or Complete failure. The error details are provided
+            in the response body and all supported error codes are documented in the
+            example.
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/DmiPluginRegistrationErrorResponse'
+              example:
+                failedCreatedCmHandles:
+                - cmHandle: my-cm-handle-01
+                  errorCode: "00"
+                  errorText: Unknown error. <error-details>
+                - cmHandle: my-cm-handle-02
+                  errorCode: "01"
+                  errorText: cm-handle already exists
+                - cmHandle: my-cm-handle-03
+                  errorCode: "03"
+                  errorText: cm-handle has an invalid character(s) in id
+                failedUpdatedCmHandles:
+                - cmHandle: my-cm-handle-01
+                  errorCode: "00"
+                  errorText: Unknown error. <error-details>
+                - cmHandle: my-cm-handle-02
+                  errorCode: "02"
+                  errorText: cm-handle does not exist
+                - cmHandle: my-cm-handle-03
+                  errorCode: "03"
+                  errorText: cm-handle has an invalid character(s) in id
+                failedRemovedCmHandles:
+                - cmHandle: my-cm-handle-01
+                  errorCode: "00"
+                  errorText: Unknown error. <error-details>
+                - cmHandle: my-cm-handle-02
+                  errorCode: "02"
+                  errorText: cm-handle does not exists
+                - cmHandle: my-cm-handle-03
+                  errorCode: "03"
+                  errorText: cm-handle has an invalid character(s) in id
+  /v1/ch/cmHandles:
+    get:
+      tags:
+      - network-cm-proxy-inventory
+      summary: "Get all cm handle IDs for a registered DMI plugin (DMI plugin, DMI\
+        \ data plugin, DMI model plugin)"
+      description: Get all cm handle IDs for a registered DMI plugin
+      operationId: getAllCmHandleIdsForRegisteredDmi
+      parameters:
+      - name: dmi-plugin-identifier
+        in: query
+        description: dmi-plugin-identifier
+        required: true
+        schema:
+          type: string
+          example: my-dmi-plugin
+      responses:
+        "200":
+          description: OK
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  type: string
+        "401":
+          description: Unauthorized
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 401
+                message: Unauthorized error message
+                details: Unauthorized error details
+        "403":
+          description: Forbidden
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 403
+                message: Forbidden error message
+                details: Forbidden error details
         "500":
           description: Internal Server Error
           content:
@@ -123,3 +207,30 @@ components:
           type: string
         details:
           type: string
+    DmiPluginRegistrationErrorResponse:
+      type: object
+      properties:
+        failedCreatedCmHandles:
+          type: array
+          items:
+            $ref: '#/components/schemas/CmHandlerRegistrationErrorResponse'
+        failedUpdatedCmHandles:
+          type: array
+          items:
+            $ref: '#/components/schemas/CmHandlerRegistrationErrorResponse'
+        failedRemovedCmHandles:
+          type: array
+          items:
+            $ref: '#/components/schemas/CmHandlerRegistrationErrorResponse'
+    CmHandlerRegistrationErrorResponse:
+      type: object
+      properties:
+        cmHandle:
+          type: string
+          example: my-cm-handle
+        errorCode:
+          type: string
+          example: "00"
+        errorText:
+          type: string
+          example: Unknown error. <error-details>
index a43190b..44cc2b8 100644 (file)
@@ -6,141 +6,21 @@ info:
 servers:
 - url: /ncmp
 paths:
-  /v1/ch/{cm-handle}/data/ds/ncmp-datastore:passthrough-operational:
+  /v1/ch/{cm-handle}/data/ds/{ncmp-datastore-name}:
     get:
       tags:
       - network-cm-proxy
-      summary: Get resource data from pass-through operational for cm handle
-      description: Get resource data from pass-through operational for given cm handle
-      operationId: getResourceDataOperationalForCmHandle
+      summary: Get resource data for cm handle
+      description: Get resource data for given cm handle
+      operationId: getResourceDataForCmHandle
       parameters:
-      - name: cm-handle
+      - name: ncmp-datastore-name
         in: path
-        description: "The identifier for a network function, network element, subnetwork\
-          \ or any other cm object by managed Network CM Proxy"
-        required: true
-        schema:
-          type: string
-          example: my-cm-handle
-      - name: resourceIdentifier
-        in: query
-        description: The format of resource identifier depend on the associated DMI
-          Plugin implementation. For ONAP DMI Plugin it will be RESTConf paths but
-          it can really be anything.
+        description: The type of the requested data
         required: true
-        allowReserved: true
         schema:
           type: string
-        examples:
-          sample 1:
-            value:
-              resourceIdentifier: \shops\bookstore
-          sample 2:
-            value:
-              resourceIdentifier: "\\shops\\bookstore\\categories[@code=1]"
-          sample 3:
-            value:
-              resourceIdentifier: "parent=shops,child=bookstore"
-      - name: options
-        in: query
-        description: "options parameter in query, it is mandatory to wrap key(s)=value(s)\
-          \ in parenthesis'()'. The format of options parameter depend on the associated\
-          \ DMI Plugin implementation."
-        required: false
-        allowReserved: true
-        schema:
-          type: string
-        examples:
-          sample 1:
-            value:
-              options: (depth=3)
-          sample 2:
-            value:
-              options: (fields=book)
-          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
-          content:
-            application/json:
-              schema:
-                type: object
-              examples:
-                dataSampleResponse:
-                  $ref: '#/components/examples/dataSampleResponse'
-        "400":
-          description: Bad Request
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorMessage'
-              example:
-                status: 400 BAD_REQUEST
-                message: Bad request error message
-                details: Bad request error details
-        "401":
-          description: Unauthorized
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorMessage'
-              example:
-                status: 401
-                message: Unauthorized error message
-                details: Unauthorized error details
-        "403":
-          description: Forbidden
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/ErrorMessage'
-              example:
-                status: 403
-                message: Forbidden error message
-                details: Forbidden error details
-        "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
-        "502":
-          description: Bad Gateway
-          content:
-            application/json:
-              schema:
-                $ref: '#/components/schemas/DmiErrorMessage'
-              example:
-                message: "Bad Gateway Error Message NCMP"
-                dmi-response:
-                  http-code: 400
-                  body: Bad Request
-
-  /v1/ch/{cm-handle}/data/ds/ncmp-datastore:passthrough-running:
-    get:
-      tags:
-      - network-cm-proxy
-      summary: Get resource data from pass-through running for cm handle
-      description: Get resource data from pass-through running for given cm handle
-      operationId: getResourceDataRunningForCmHandle
-      parameters:
+          example: ncmp-datastore:operational
       - name: cm-handle
         in: path
         description: "The identifier for a network function, network element, subnetwork\
@@ -198,6 +78,13 @@ paths:
           sample 1:
             value:
               topic: my-topic-name
+      - name: include-descendants
+        in: query
+        description: Determines if descendants are included in response
+        required: false
+        schema:
+          type: boolean
+          default: false
       responses:
         "200":
           description: OK
@@ -255,7 +142,7 @@ paths:
               schema:
                 $ref: '#/components/schemas/DmiErrorMessage'
               example:
-                message: "Bad Gateway Error Message NCMP"
+                message: Bad Gateway Error Message NCMP
                 dmi-response:
                   http-code: 400
                   body: Bad Request
@@ -267,6 +154,13 @@ paths:
         handle
       operationId: updateResourceDataRunningForCmHandle
       parameters:
+      - name: ncmp-datastore-name
+        in: path
+        description: The type of the requested data
+        required: true
+        schema:
+          type: string
+          example: ncmp-datastore:operational
       - name: cm-handle
         in: path
         description: "The identifier for a network function, network element, subnetwork\
@@ -372,7 +266,7 @@ paths:
               schema:
                 $ref: '#/components/schemas/DmiErrorMessage'
               example:
-                message: "Bad Gateway Error Message NCMP"
+                message: Bad Gateway Error Message NCMP
                 dmi-response:
                   http-code: 400
                   body: Bad Request
@@ -383,6 +277,13 @@ paths:
       description: create resource data from pass-through running for given cm handle
       operationId: createResourceDataRunningForCmHandle
       parameters:
+      - name: ncmp-datastore-name
+        in: path
+        description: The type of the requested data
+        required: true
+        schema:
+          type: string
+          example: ncmp-datastore:operational
       - name: cm-handle
         in: path
         description: "The identifier for a network function, network element, subnetwork\
@@ -485,7 +386,7 @@ paths:
               schema:
                 $ref: '#/components/schemas/DmiErrorMessage'
               example:
-                message: "Bad Gateway Error Message NCMP"
+                message: Bad Gateway Error Message NCMP
                 dmi-response:
                   http-code: 400
                   body: Bad Request
@@ -496,6 +397,13 @@ paths:
       description: Delete resource data from pass-through running for a given cm handle
       operationId: deleteResourceDataRunningForCmHandle
       parameters:
+      - name: ncmp-datastore-name
+        in: path
+        description: The type of the requested data
+        required: true
+        schema:
+          type: string
+          example: ncmp-datastore:operational
       - name: cm-handle
         in: path
         description: "The identifier for a network function, network element, subnetwork\
@@ -593,7 +501,7 @@ paths:
               schema:
                 $ref: '#/components/schemas/DmiErrorMessage'
               example:
-                message: "Bad Gateway Error Message NCMP"
+                message: Bad Gateway Error Message NCMP
                 dmi-response:
                   http-code: 400
                   body: Bad Request
@@ -605,6 +513,13 @@ paths:
         handle
       operationId: patchResourceDataRunningForCmHandle
       parameters:
+      - name: ncmp-datastore-name
+        in: path
+        description: The type of the requested data
+        required: true
+        schema:
+          type: string
+          example: ncmp-datastore:operational
       - name: cm-handle
         in: path
         description: "The identifier for a network function, network element, subnetwork\
@@ -704,7 +619,7 @@ paths:
               schema:
                 $ref: '#/components/schemas/DmiErrorMessage'
               example:
-                message: "Bad Gateway Error Message NCMP"
+                message: Bad Gateway Error Message NCMP
                 dmi-response:
                   http-code: 400
                   body: Bad Request
@@ -774,19 +689,95 @@ paths:
                 status: 500
                 message: Internal Server Error
                 details: Internal Server Error occurred
+  /v1/ch/{cm-handle}/modules/definitions:
+    get:
+      tags:
+      - network-cm-proxy
+      summary: "Fetch all module definitions (name, revision, yang resource) for a\
+        \ given cm handle"
+      description: "Fetch all module definitions (name, revision, yang resource) for\
+        \ a given cm handle"
+      operationId: getModuleDefinitionsByCmHandleId
+      parameters:
+      - name: cm-handle
+        in: path
+        description: "The identifier for a network function, network element, subnetwork\
+          \ or any other cm object by managed Network CM Proxy"
+        required: true
+        schema:
+          type: string
+          example: my-cm-handle
+      responses:
+        "200":
+          description: OK
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/RestModuleDefinition'
+        "401":
+          description: Unauthorized
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 401
+                message: Unauthorized error message
+                details: Unauthorized error details
+        "403":
+          description: Forbidden
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 403
+                message: Forbidden error message
+                details: Forbidden error details
+        "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/ch/searches:
     post:
       tags:
       - network-cm-proxy
       summary: Execute cm handle search using the available conditions
-      description: Execute cm handle searches using 'hasAllModules' condition to get
-        all cm handles for the given module names
-      operationId: executeCmHandleSearch
+      description: Execute cm handle query search and return a list of cm handle details.
+        Any number of conditions can be applied. To be included in the result a cm-handle
+        must fulfill ALL the conditions. An empty collection will be returned in the
+        case that the cm handle does not match a condition. For more on cm handle
+        query search please refer to <a href="https://docs.onap.org/projects/onap-cps/en/latest/ncmp-cmhandle-querying.html">cm
+        handle query search Read the Docs</a>.<br/>By supplying a CPS Path it is possible
+        to query on any data related to the cm handle. For more on CPS Path please
+        refer to <a href="https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html">CPS
+        Path Read the Docs</a>. The cm handle ancestor is automatically returned for
+        this query.
+      operationId: searchCmHandles
       requestBody:
         content:
           application/json:
             schema:
-              $ref: '#/components/schemas/Conditions'
+              $ref: '#/components/schemas/CmHandleQueryParameters'
+            examples:
+              Cm handle properties query:
+                $ref: '#/components/examples/pubPropCmHandleQueryParameters'
+              Cm handle modules query:
+                $ref: '#/components/examples/modulesCmHandleQueryParameters'
+              All cm handle query parameters:
+                $ref: '#/components/examples/allCmHandleQueryParameters'
+              Cm handle with CPS path state query:
+                $ref: '#/components/examples/cpsPathCmHandleStateQueryParameters'
+              Cm handle with data sync flag query:
+                $ref: '#/components/examples/cpsPathCmHandleDataSyncQueryParameters'
         required: true
       responses:
         "200":
@@ -794,7 +785,9 @@ paths:
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/CmHandles'
+                type: array
+                items:
+                  $ref: '#/components/schemas/RestOutputCmHandle'
         "400":
           description: Bad Request
           content:
@@ -878,6 +871,151 @@ paths:
                 status: 401
                 message: Unauthorized error message
                 details: Unauthorized error details
+        "404":
+          description: The specified resource was not found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 400
+                message: Not found error message
+                details: Not found error details
+        "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/ch/{cm-handle}/properties:
+    get:
+      tags:
+      - network-cm-proxy
+      summary: Get CM handle properties
+      description: Get CM handle properties by cm handle id
+      operationId: getCmHandlePublicPropertiesByCmHandleId
+      parameters:
+      - name: cm-handle
+        in: path
+        description: "The identifier for a network function, network element, subnetwork\
+          \ or any other cm object by managed Network CM Proxy"
+        required: true
+        schema:
+          type: string
+          example: my-cm-handle
+      responses:
+        "200":
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/RestOutputCmHandlePublicProperties'
+        "400":
+          description: Bad Request
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 400 BAD_REQUEST
+                message: Bad request error message
+                details: Bad request error details
+        "401":
+          description: Unauthorized
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 401
+                message: Unauthorized error message
+                details: Unauthorized error details
+        "404":
+          description: The specified resource was not found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 400
+                message: Not found error message
+                details: Not found error details
+        "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/ch/id-searches:
+    post:
+      tags:
+      - network-cm-proxy
+      summary: Execute cm handle query upon a given set of query parameters
+      description: Execute cm handle query search and return a list of cm handle ids.
+        Any number of conditions can be applied. To be included in the result a cm-handle
+        must fulfill ALL the conditions. An empty collection will be returned in the
+        case that the cm handle does not match a condition. For more on cm handle
+        query search please refer to <a href="https://docs.onap.org/projects/onap-cps/en/latest/ncmp-cmhandle-querying.html">cm
+        handle query search Read the Docs</a>.<br/>By supplying a CPS Path it is possible
+        to query on any data related to the cm handle. For more on CPS Path please
+        refer to <a href="https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html">CPS
+        Path Read the Docs</a>. The cm handle ancestor is automatically returned for
+        this query.
+      operationId: searchCmHandleIds
+      requestBody:
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/CmHandleQueryParameters'
+            examples:
+              Cm handle properties query:
+                $ref: '#/components/examples/pubPropCmHandleQueryParameters'
+              Cm handle modules query:
+                $ref: '#/components/examples/modulesCmHandleQueryParameters'
+              All cm handle query parameters:
+                $ref: '#/components/examples/allCmHandleQueryParameters'
+              Cm handle with CPS path state query:
+                $ref: '#/components/examples/cpsPathCmHandleStateQueryParameters'
+              Cm handle with data sync flag query:
+                $ref: '#/components/examples/cpsPathCmHandleDataSyncQueryParameters'
+        required: true
+      responses:
+        "200":
+          description: OK
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  type: string
+        "400":
+          description: Bad Request
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 400 BAD_REQUEST
+                message: Bad request error message
+                details: Bad request error details
+        "401":
+          description: Unauthorized
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 401
+                message: Unauthorized error message
+                details: Unauthorized error details
         "403":
           description: Forbidden
           content:
@@ -908,6 +1046,152 @@ paths:
                 status: 500
                 message: Internal Server Error
                 details: Internal Server Error occurred
+  /v1/ch/{cm-handle}/state:
+    get:
+      tags:
+      - network-cm-proxy
+      summary: Get CM handle state
+      description: Get CM handle state by cm handle id
+      operationId: getCmHandleStateByCmHandleId
+      parameters:
+      - name: cm-handle
+        in: path
+        description: "The identifier for a network function, network element, subnetwork\
+          \ or any other cm object by managed Network CM Proxy"
+        required: true
+        schema:
+          type: string
+          example: my-cm-handle
+      responses:
+        "200":
+          description: OK
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/RestOutputCmHandleCompositeState'
+        "400":
+          description: Bad Request
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 400 BAD_REQUEST
+                message: Bad request error message
+                details: Bad request error details
+        "401":
+          description: Unauthorized
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 401
+                message: Unauthorized error message
+                details: Unauthorized error details
+        "404":
+          description: The specified resource was not found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 400
+                message: Not found error message
+                details: Not found error details
+        "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/ch/{cm-handle}/data-sync:
+    put:
+      tags:
+      - network-cm-proxy
+      summary: Set the Data Sync Enabled Flag
+      description: Set the data sync enabled flag to true or false for a specified
+        Cm-Handle. This will in turn set the data sync state to UNSYNCHRONIZED and
+        NONE_REQUESTED respectfully.
+      operationId: setDataSyncEnabledFlagForCmHandle
+      parameters:
+      - name: cm-handle
+        in: path
+        description: "The identifier for a network function, network element, subnetwork\
+          \ or any other cm object by managed Network CM Proxy"
+        required: true
+        schema:
+          type: string
+          example: my-cm-handle
+      - name: dataSyncEnabled
+        in: query
+        description: Is used to enable or disable the data synchronization flag
+        required: true
+        schema:
+          type: boolean
+          example: true
+      responses:
+        "200":
+          description: OK
+          content:
+            application/json:
+              schema:
+                type: object
+        "400":
+          description: Bad Request
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 400 BAD_REQUEST
+                message: Bad request error message
+                details: Bad request error details
+        "401":
+          description: Unauthorized
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 401
+                message: Unauthorized error message
+                details: Unauthorized error details
+        "403":
+          description: Forbidden
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ErrorMessage'
+              example:
+                status: 403
+                message: Forbidden error message
+                details: Forbidden error details
+        "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
+        "502":
+          description: Bad Gateway
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/DmiErrorMessage'
+              example:
+                message: Bad Gateway Error Message NCMP
+                dmi-response:
+                  http-code: 400
+                  body: Bad Request
 components:
   schemas:
     ErrorMessage:
@@ -920,23 +1204,15 @@ components:
           type: string
         details:
           type: string
-    # DMI Server Exception Schema
     DmiErrorMessage:
       title: DMI Error Message
       type: object
       properties:
         message:
           type: string
-          example: "Bad Gateway Error Message NCMP"
+          example: Bad Gateway Error Message NCMP
         dmi-response:
-          type: object
-          properties:
-            http-code:
-              type: integer
-              example: 400
-            body:
-              type: string
-              example: Bad Request
+          $ref: '#/components/schemas/DmiErrorMessage_dmiresponse'
     RestModuleReference:
       title: Module reference details
       type: object
@@ -947,45 +1223,59 @@ components:
         revision:
           type: string
           example: my-module-revision
-    Conditions:
+    RestModuleDefinition:
+      title: Module definitions
+      type: object
+      properties:
+        moduleName:
+          type: string
+          example: my-module-name
+        revision:
+          type: string
+          example: 2020-09-15T00:00:00.000+00:00
+        content:
+          type: string
+          example: "module stores {\n  yang-version 1.1;\n  namespace \"org:onap:ccsdk:sample\"\
+            ;\n  prefix book-store;\n  revision \"2020-09-15\" {\n    description\n\
+            \    \"Sample Model\";\n  }\n}\n"
+    CmHandleQueryParameters:
+      title: Cm Handle query parameters for executing cm handle search
       type: object
       properties:
+        cmHandleQueryParameters:
+          type: array
+          items:
+            $ref: '#/components/schemas/ConditionProperties'
         conditions:
-          $ref: '#/components/schemas/ConditionsData'
-    ConditionsData:
-      type: array
-      items:
-        $ref: '#/components/schemas/ConditionProperties'
+          type: array
+          description: "not necessary, it is just for backward compatibility"
+          deprecated: true
+          items:
+            $ref: '#/components/schemas/OldConditionProperties'
     ConditionProperties:
+      properties:
+        conditionName:
+          type: string
+        conditionParameters:
+          type: array
+          items:
+            type: object
+            additionalProperties:
+              type: string
+    OldConditionProperties:
       properties:
         name:
           type: string
-          example: hasAllModules
         conditionParameters:
-          $ref: '#/components/schemas/ModuleNamesAsJsonArray'
-    ModuleNamesAsJsonArray:
-      type: array
-      items:
-        $ref: '#/components/schemas/ModuleNameAsJsonObject'
+          type: array
+          items:
+            $ref: '#/components/schemas/ModuleNameAsJsonObject'
+      deprecated: true
     ModuleNameAsJsonObject:
       properties:
         moduleName:
           type: string
           example: my-module
-    CmHandles:
-      type: object
-      properties:
-        cmHandles:
-          $ref: '#/components/schemas/CmHandleProperties'
-    CmHandleProperties:
-      type: array
-      items:
-        $ref: '#/components/schemas/CmHandleProperty'
-    CmHandleProperty:
-      properties:
-        cmHandleId:
-          type: string
-          example: my-cm-handle-id
     RestOutputCmHandle:
       title: CM handle Details
       type: object
@@ -995,6 +1285,8 @@ components:
           example: my-cm-handle1
         publicCmHandleProperties:
           $ref: '#/components/schemas/CmHandlePublicProperties'
+        state:
+          $ref: '#/components/schemas/CmHandleCompositeState'
     CmHandlePublicProperties:
       type: array
       items:
@@ -1002,6 +1294,66 @@ components:
         additionalProperties:
           type: string
           example: Book Type
+    CmHandleCompositeState:
+      type: object
+      properties:
+        cmHandleState:
+          type: string
+          example: ADVISED
+        lockReason:
+          $ref: '#/components/schemas/lock-reason'
+        lastUpdateTime:
+          type: string
+          example: 2022-12-31T20:30:40.000+0000
+        dataSyncEnabled:
+          type: boolean
+          example: false
+        dataSyncState:
+          $ref: '#/components/schemas/dataStores'
+    lock-reason:
+      type: object
+      properties:
+        reason:
+          type: string
+          example: LOCKED_MISBEHAVING
+        details:
+          type: string
+          example: locked due to failure in module sync
+    dataStores:
+      type: object
+      properties:
+        operational:
+          $ref: '#/components/schemas/sync-state'
+        running:
+          $ref: '#/components/schemas/sync-state'
+    sync-state:
+      type: object
+      properties:
+        syncState:
+          type: string
+          example: NONE_REQUESTED
+        lastSyncTime:
+          type: string
+          example: 2022-12-31T20:30:40.000+0000
+    RestOutputCmHandlePublicProperties:
+      type: object
+      properties:
+        publicCmHandleProperties:
+          $ref: '#/components/schemas/CmHandlePublicProperties'
+    RestOutputCmHandleCompositeState:
+      type: object
+      properties:
+        state:
+          $ref: '#/components/schemas/CmHandleCompositeState'
+    DmiErrorMessage_dmiresponse:
+      type: object
+      properties:
+        http-code:
+          type: integer
+          example: 400
+        body:
+          type: string
+          example: Bad Request
   examples:
     dataSampleResponse:
       summary: Sample response
@@ -1081,3 +1433,47 @@ components:
                   books:
                   - authors:
                     - Philip Pullman
+    pubPropCmHandleQueryParameters:
+      value:
+        cmHandleQueryParameters:
+        - conditionName: hasAllProperties
+          conditionParameters:
+          - Color: yellow
+          - Shape: circle
+          - Size: small
+    modulesCmHandleQueryParameters:
+      value:
+        cmHandleQueryParameters:
+        - conditionName: hasAllModules
+          conditionParameters:
+          - moduleName: my-module-1
+          - moduleName: my-module-2
+          - moduleName: my-module-3
+    allCmHandleQueryParameters:
+      value:
+        cmHandleQueryParameters:
+        - conditionName: hasAllModules
+          conditionParameters:
+          - moduleName: my-module-1
+          - moduleName: my-module-2
+          - moduleName: my-module-3
+        - conditionName: hasAllProperties
+          conditionParameters:
+          - Color: yellow
+          - Shape: circle
+          - Size: small
+        - conditionName: cmHandleWithCpsPath
+          conditionParameters:
+          - cpsPath: "//state[@cm-handle-state='ADVISED']"
+    cpsPathCmHandleStateQueryParameters:
+      value:
+        cmHandleQueryParameters:
+        - conditionName: cmHandleWithCpsPath
+          conditionParameters:
+          - cpsPath: "//state[@cm-handle-state='LOCKED']"
+    cpsPathCmHandleDataSyncQueryParameters:
+      value:
+        cmHandleQueryParameters:
+        - conditionName: cmHandleWithCpsPath
+          conditionParameters:
+          - cpsPath: "//state[@data-sync-enabled='true']"
index 58dc060..d4f9843 100755 (executable)
@@ -39,6 +39,11 @@ Release Data
 Features
 --------
    - `CPS-322 <https://jira.onap.org/browse/CPS-322>`_  Implement additional validation for names and identifiers
+   - `CPS-1136 <https://jira.onap.org/browse/CPS-1136>`_  Get all cm handles by DMI plugin Identifier
+   - `CPS-1001 <https://jira.onap.org/browse/CPS-1001>`_  Add CPS-E-05 endpoint for Read data, NCMP-Operational Datastore
+
+Bug Fixes
+---------
 
 ..      ========================
 ..      * * *   JAKARTA   * * *
diff --git a/releases/3.1.0-container.yaml b/releases/3.1.0-container.yaml
new file mode 100644 (file)
index 0000000..cc11ed2
--- /dev/null
@@ -0,0 +1,8 @@
+distribution_type: container
+container_release_tag: 3.1.0
+project: cps
+log_dir: cps-maven-docker-stage-master/690/
+ref: 9697e76c319e4cf59fc494216a720393545503a9
+containers:
+  - name: 'cps-and-ncmp'
+    version: '3.1.0-20220914T140025Z'
diff --git a/releases/3.1.0.yaml b/releases/3.1.0.yaml
new file mode 100644 (file)
index 0000000..1258b26
--- /dev/null
@@ -0,0 +1,4 @@
+distribution_type: maven
+log_dir: cps-maven-stage-master/696/
+project: cps
+version: 3.1.0