NCMP: Update existing Batch endpoint (Moving url param into rest body) 30/134530/12
authorsourabh_sourabh <sourabh.sourabh@est.tech>
Tue, 9 May 2023 13:34:59 +0000 (14:34 +0100)
committersourabh_sourabh <sourabh.sourabh@est.tech>
Thu, 25 May 2023 14:29:37 +0000 (15:29 +0100)
- NCMP batch endpoint is updated to accept details into request payload.
- Removed unused code of previous impl.

Issue-ID: CPS-1635

Signed-off-by: sourabh_sourabh <sourabh.sourabh@est.tech>
Change-Id: Ic290b750557da06b861c5a4a9bb12debc495ec2e
Signed-off-by: sourabh_sourabh <sourabh.sourabh@est.tech>
36 files changed:
cps-ncmp-rest-stub/src/main/java/org/onap/cps/ncmp/rest/stub/handlers/NetworkCmProxyApiStubDefaultImpl.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/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpDatastoreRequestHandler.java
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/NcmpPassthroughResourceRequestHandler.java
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/handlers/TaskManagementDefaultHandler.java
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/exceptions/OperationNotSupportedException.java [moved from cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/OperationEnum.java with 67% similarity]
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/mapper/ResourceDataBatchRequestMapper.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/exceptions/NetworkCmProxyRestExceptionHandlerSpec.groovy
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/exception/InvalidOperationException.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/CmHandle.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiBatchOperation.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiDataOperations.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiModelOperations.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiRequestBody.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/OperationType.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/DmiServiceUrlBuilder.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/ResourceDataBatchRequestUtils.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/BatchOperationDefinition.java [new file with mode: 0644]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/ResourceDataBatchRequest.java [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/client/DmiRestClientSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiDataOperationsSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/operations/DmiModelOperationsSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/ResourceDataBatchRequestUtilsSpec.groovy [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/RestQueryParametersValidatorSpec.groovy
cps-ncmp-service/src/test/resources/resourceDataBatchRequest.json [new file with mode: 0644]
dmi-plugin-stub/files/batchResponse.json
dmi-plugin-stub/mappings/batchCmHandles.json

index 6e28dbc..7bd3acb 100644 (file)
@@ -23,6 +23,7 @@ package org.onap.cps.ncmp.rest.stub.handlers;
 import java.util.List;
 import org.onap.cps.ncmp.rest.api.NetworkCmProxyApi;
 import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters;
+import org.onap.cps.ncmp.rest.model.ResourceDataBatchRequest;
 import org.onap.cps.ncmp.rest.model.RestModuleDefinition;
 import org.onap.cps.ncmp.rest.model.RestModuleReference;
 import org.onap.cps.ncmp.rest.model.RestOutputCmHandle;
@@ -46,12 +47,9 @@ public interface NetworkCmProxyApiStubDefaultImpl extends NetworkCmProxyApi {
     }
 
     @Override
-    default ResponseEntity<Object> getResourceDataForCmHandleBatch(final String resourceIdentifier,
-                                                                   final String topicParamInQuery,
-                                                                   final String datastoreName,
-                                                                   final Object requestBody,
-                                                                   final String optionsParamInQuery,
-                                                                   final Boolean includeDescendants) {
+    default ResponseEntity<Object> getResourceDataForCmHandleBatch(final String topicParamInQuery,
+                                                                   final ResourceDataBatchRequest
+                                                                           resourceDataBatchRequest) {
         return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
     }
 
index 7fc1063..2781f57 100644 (file)
@@ -285,6 +285,43 @@ components:
       properties:
         state:
           $ref: '#/components/schemas/CmHandleCompositeState'
+    # Batch Request Schemas
+    ResourceDataBatchRequest:
+      type: object
+      title: get resource data for given array of operations
+      properties:
+        operations:
+          type: array
+          items:
+            type: object
+            $ref: '#/components/schemas/BatchOperationDefinition'
+          description: contains batch request details
+    BatchOperationDefinition:
+      required:
+        - operation
+        - datastore
+        - operationId
+      properties:
+        operation:
+          type: string
+          example: 'read'
+        operationId:
+          type: string
+          example: '12'
+        datastore:
+          type: string
+          example: 'ncmp-datastore:passthrough-operational'
+        options:
+          type: string
+          example: '(fields=schemas/schema)'
+        resourceIdentifier:
+          type: string
+          example: 'parent/child'
+        targetIds:
+          type: array
+          items:
+            type: string
+          example: [ "da310eecdb8d44c2acc0ddaae01174b1","c748c58f8e0b438f9fd1f28370b17d47" ]
 
   examples:
     dataSampleRequest:
index 2b70d94..957a3b8 100755 (executable)
@@ -202,17 +202,13 @@ getResourceDataForCmHandleBatch:
     description: This request will be handled asynchronously using messaging to the supplied topic. The rest response will be an acknowledge with a requestId to identify the relevant messages.
     operationId: getResourceDataForCmHandleBatch
     parameters:
-      - $ref: 'components.yaml#/components/parameters/datastoreName'
-      - $ref: 'components.yaml#/components/parameters/resourceIdentifierInQuery'
-      - $ref: 'components.yaml#/components/parameters/optionsParamInQuery'
       - $ref: 'components.yaml#/components/parameters/requiredTopicParamInQuery'
-      - $ref: 'components.yaml#/components/parameters/includeDescendantsOptionInQuery'
     requestBody:
       required: true
       content:
         application/json:
           schema:
-            type: object
+            $ref: 'components.yaml#/components/schemas/ResourceDataBatchRequest'
     responses:
       200:
         description: OK
index 5b4c0d3..b63b568 100755 (executable)
@@ -34,7 +34,7 @@ paths:
   /v1/ch/{cm-handle}/data/ds/{datastore-name}:
     $ref: 'ncmp.yml#/resourceDataForCmHandle'
 
-  /v1/batch/data/ds/{datastore-name}:
+  /v1/data:
     $ref: 'ncmp.yml#/getResourceDataForCmHandleBatch'
 
   /v1/ch/{cm-handle}/data/ds/{datastore-name}/query:
index fca1d63..1b78fa0 100755 (executable)
 
 package org.onap.cps.ncmp.rest.controller;
 
-import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.CREATE;
-import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.DELETE;
-import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.PATCH;
-import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.UPDATE;
+import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.OPERATIONAL;
+import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING;
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE;
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.DELETE;
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.PATCH;
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE;
 
 import java.util.Collection;
 import java.util.List;
@@ -44,8 +46,10 @@ import org.onap.cps.ncmp.rest.controller.handlers.NcmpCachedResourceRequestHandl
 import org.onap.cps.ncmp.rest.controller.handlers.NcmpDatastoreRequestHandler;
 import org.onap.cps.ncmp.rest.controller.handlers.NcmpPassthroughResourceRequestHandler;
 import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper;
+import org.onap.cps.ncmp.rest.mapper.ResourceDataBatchRequestMapper;
 import org.onap.cps.ncmp.rest.model.CmHandlePublicProperties;
 import org.onap.cps.ncmp.rest.model.CmHandleQueryParameters;
+import org.onap.cps.ncmp.rest.model.ResourceDataBatchRequest;
 import org.onap.cps.ncmp.rest.model.RestModuleDefinition;
 import org.onap.cps.ncmp.rest.model.RestModuleReference;
 import org.onap.cps.ncmp.rest.model.RestOutputCmHandle;
@@ -72,6 +76,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
     private final CmHandleStateMapper cmHandleStateMapper;
     private final NcmpCachedResourceRequestHandler ncmpCachedResourceRequestHandler;
     private final NcmpPassthroughResourceRequestHandler ncmpPassthroughResourceRequestHandler;
+    private final ResourceDataBatchRequestMapper resourceDataBatchRequestMapper;
 
     /**
      * Get resource data from datastore.
@@ -100,19 +105,11 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
     }
 
     @Override
-    public ResponseEntity<Object> getResourceDataForCmHandleBatch(final String resourceIdentifier,
-                                                                  final String topicParamInQuery,
-                                                                  final String datastoreName,
-                                                                  final Object requestBody,
-                                                                  final String optionsParamInQuery,
-                                                                  final Boolean includeDescendants) {
-
-        final NcmpDatastoreRequestHandler ncmpDatastoreRequestHandler = getNcmpDatastoreRequestHandler(datastoreName);
-
-        final List<String> cmHandleIds = jsonObjectMapper.convertJsonString(jsonObjectMapper.asJsonString(requestBody),
-                List.class);
-        return ncmpDatastoreRequestHandler.executeRequest(datastoreName, cmHandleIds, resourceIdentifier,
-                optionsParamInQuery, topicParamInQuery, includeDescendants);
+    public ResponseEntity<Object> getResourceDataForCmHandleBatch(final String topicParamInQuery,
+                                                                  final ResourceDataBatchRequest
+                                                                          resourceDataBatchRequest) {
+        return ncmpPassthroughResourceRequestHandler.executeRequest(topicParamInQuery,
+                resourceDataBatchRequestMapper.toResourceDataBatchRequest(resourceDataBatchRequest));
     }
 
     /**
@@ -134,7 +131,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
                                                                final String optionsParamInQuery,
                                                                final String topicParamInQuery,
                                                                final Boolean includeDescendants) {
-        validateDataStore(DatastoreType.OPERATIONAL, datastoreName);
+        validateDataStore(OPERATIONAL, datastoreName);
         return ncmpCachedResourceRequestHandler.executeRequest(cmHandle, cpsPath, includeDescendants);
     }
 
@@ -156,7 +153,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
                                                                       final Object requestBody,
                                                                       final String contentType) {
 
-        validateDataStore(DatastoreType.PASSTHROUGH_RUNNING, datastoreName);
+        validateDataStore(PASSTHROUGH_RUNNING, datastoreName);
 
         final Object responseObject = networkCmProxyDataService
                 .writeResourceDataPassThroughRunningForCmHandle(
@@ -182,7 +179,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
                                                                      final Object requestBody,
                                                                      final String contentType) {
 
-        validateDataStore(DatastoreType.PASSTHROUGH_RUNNING, datastoreName);
+        validateDataStore(PASSTHROUGH_RUNNING, datastoreName);
 
         networkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle(cmHandle,
                 resourceIdentifier, CREATE, jsonObjectMapper.asJsonString(requestBody), contentType);
@@ -206,7 +203,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
                                                                        final String cmHandle,
                                                                        final Object requestBody,
                                                                        final String contentType) {
-        validateDataStore(DatastoreType.PASSTHROUGH_RUNNING, datastoreName);
+        validateDataStore(PASSTHROUGH_RUNNING, datastoreName);
 
         networkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle(cmHandle,
                 resourceIdentifier, UPDATE, jsonObjectMapper.asJsonString(requestBody), contentType);
@@ -228,7 +225,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
                                                                      final String resourceIdentifier,
                                                                      final String contentType) {
 
-        validateDataStore(DatastoreType.PASSTHROUGH_RUNNING, datastoreName);
+        validateDataStore(PASSTHROUGH_RUNNING, datastoreName);
 
         networkCmProxyDataService.writeResourceDataPassThroughRunningForCmHandle(cmHandle,
                 resourceIdentifier, DELETE, NO_BODY, contentType);
@@ -381,7 +378,7 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
     }
 
     private NcmpDatastoreRequestHandler getNcmpDatastoreRequestHandler(final String datastoreName) {
-        if (DatastoreType.OPERATIONAL.equals(DatastoreType.fromDatastoreName(datastoreName))) {
+        if (OPERATIONAL.equals(DatastoreType.fromDatastoreName(datastoreName))) {
             return ncmpCachedResourceRequestHandler;
         }
         return ncmpPassthroughResourceRequestHandler;
index a32c462..a8ca13a 100644 (file)
 
 package org.onap.cps.ncmp.rest.controller.handlers;
 
-import java.util.List;
+import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.OPERATIONAL;
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ;
+
 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.impl.exception.InvalidDatastoreException;
+import org.onap.cps.ncmp.api.impl.operations.DatastoreType;
+import org.onap.cps.ncmp.api.impl.operations.OperationType;
+import org.onap.cps.ncmp.api.models.ResourceDataBatchRequest;
+import org.onap.cps.ncmp.rest.exceptions.OperationNotSupportedException;
 import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor;
 import org.onap.cps.ncmp.rest.util.TopicValidator;
 import org.springframework.beans.factory.annotation.Value;
@@ -67,7 +74,7 @@ public class NcmpDatastoreRequestHandler implements TaskManagementDefaultHandler
         final boolean asyncResponseRequested = topicParamInQuery != null;
         if (asyncResponseRequested && notificationFeatureEnabled) {
             return executeAsyncTaskAndGetResponseEntity(datastoreName, cmHandleId, resourceIdentifier,
-                optionsParamInQuery, topicParamInQuery, includeDescendants, false);
+                optionsParamInQuery, topicParamInQuery, includeDescendants);
         }
 
         if (asyncResponseRequested) {
@@ -98,26 +105,21 @@ public class NcmpDatastoreRequestHandler implements TaskManagementDefaultHandler
     }
 
     /**
-     * Executes synchronous/asynchronous request for batch of cm handles.
+     * Executes asynchronous request for batch of cm handles to resource data.
      *
-     * @param datastoreName       the name of the datastore
-     * @param cmHandleIds         list of cm handles
-     * @param resourceIdentifier  the resource identifier
-     * @param optionsParamInQuery the options param in query
-     * @param topicParamInQuery   the topic param in query
-     * @param includeDescendants  whether to include descendants or not
+     * @param topicParamInQuery        the topic param in query
+     * @param resourceDataBatchRequest batch request details for resource data
      * @return the response entity
      */
-    public ResponseEntity<Object> executeRequest(final String datastoreName,
-                                                 final List<String> cmHandleIds,
-                                                 final String resourceIdentifier,
-                                                 final String optionsParamInQuery,
-                                                 final String topicParamInQuery,
-                                                 final boolean includeDescendants) {
-
-        return executeAsyncTaskAndGetResponseEntity(datastoreName, cmHandleIds, resourceIdentifier, optionsParamInQuery,
-                topicParamInQuery, includeDescendants, true);
-
+    public ResponseEntity<Object> executeRequest(final String topicParamInQuery,
+                                                 final ResourceDataBatchRequest
+                                                         resourceDataBatchRequest) {
+        validateBatchRequest(topicParamInQuery, resourceDataBatchRequest);
+        if (!notificationFeatureEnabled) {
+            return ResponseEntity.ok(Map.of("status",
+                    "Asynchronous request is unavailable as notification feature is currently disabled."));
+        }
+        return getRequestIdAndSendBatchRequestToDmiService(topicParamInQuery, resourceDataBatchRequest);
     }
 
     protected ResponseEntity<Object> executeTaskAsync(final String topicParamInQuery,
@@ -127,7 +129,6 @@ public class NcmpDatastoreRequestHandler implements TaskManagementDefaultHandler
         TopicValidator.validateTopicName(topicParamInQuery);
         log.debug("Received Async request with id {}", requestId);
         cpsNcmpTaskExecutor.executeTask(taskSupplier, timeOutInMilliSeconds);
-
         return ResponseEntity.ok(Map.of("requestId", requestId));
     }
 
@@ -136,25 +137,43 @@ public class NcmpDatastoreRequestHandler implements TaskManagementDefaultHandler
     }
 
     private ResponseEntity<Object> executeAsyncTaskAndGetResponseEntity(final String datastoreName,
-                                                                        final Object targetObject,
+                                                                        final String cmHandleId,
                                                                         final String resourceIdentifier,
                                                                         final String optionsParamInQuery,
                                                                         final String topicParamInQuery,
-                                                                        final boolean includeDescendants,
-                                                                        final boolean isBulkRequest) {
+                                                                        final boolean includeDescendants) {
         final String requestId = UUID.randomUUID().toString();
-        final Supplier<Object> taskSupplier;
-        if (isBulkRequest) {
-            taskSupplier = getTaskSupplierForBulkRequest(datastoreName, (List<String>) targetObject,
-                    resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId, includeDescendants);
-        } else {
-            taskSupplier = getTaskSupplierForGetRequest(datastoreName, targetObject.toString(), resourceIdentifier,
-                    optionsParamInQuery, topicParamInQuery, requestId, includeDescendants);
-        }
+        final Supplier<Object> taskSupplier = getTaskSupplierForGetRequest(datastoreName, cmHandleId,
+                resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId, includeDescendants);
         if (taskSupplier == NO_OBJECT_SUPPLIER) {
             return new ResponseEntity<>(Map.of("status", "Unable to execute request as "
                     + "datastore is not implemented."), HttpStatus.NOT_IMPLEMENTED);
         }
         return executeTaskAsync(topicParamInQuery, requestId, taskSupplier);
     }
+
+    private ResponseEntity<Object> getRequestIdAndSendBatchRequestToDmiService(final String topicParamInQuery,
+                                                                               final ResourceDataBatchRequest
+                                                                                       resourceDataBatchRequest) {
+        final String requestId = UUID.randomUUID().toString();
+        sendResourceDataBatchRequestAsynchronously(topicParamInQuery, resourceDataBatchRequest, requestId);
+        return ResponseEntity.ok(Map.of("requestId", requestId));
+    }
+
+    private void validateBatchRequest(final String topicParamInQuery,
+                                      final ResourceDataBatchRequest
+                                              resourceDataBatchRequest) {
+        TopicValidator.validateTopicName(topicParamInQuery);
+        resourceDataBatchRequest.getBatchOperationDefinitions().forEach(batchOperationDetail -> {
+            if (OperationType.fromOperationName(batchOperationDetail.getOperation()) != READ) {
+                throw new OperationNotSupportedException(
+                        batchOperationDetail.getOperation() + " operation not yet supported for target ids :"
+                                + batchOperationDetail.getCmHandleIds());
+            } else if (DatastoreType.fromDatastoreName(batchOperationDetail.getDatastore()) == OPERATIONAL) {
+                throw new InvalidDatastoreException(batchOperationDetail.getDatastore()
+                        + " datastore is not supported for target ids : "
+                        + batchOperationDetail.getCmHandleIds());
+            }
+        });
+    }
 }
index 18e5a9f..5c35818 100644 (file)
 
 package org.onap.cps.ncmp.rest.controller.handlers;
 
-import java.util.List;
 import java.util.function.Supplier;
 import org.onap.cps.ncmp.api.NetworkCmProxyDataService;
+import org.onap.cps.ncmp.api.models.ResourceDataBatchRequest;
 import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Component;
 
 @Component
@@ -56,17 +57,14 @@ public class NcmpPassthroughResourceRequestHandler extends NcmpDatastoreRequestH
                 datastoreName, cmHandleId, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId);
     }
 
+    @Async
     @Override
-    public Supplier<Object> getTaskSupplierForBulkRequest(final String datastoreName,
-                                                          final List<String> cmHandleIds,
-                                                          final String resourceIdentifier,
-                                                          final String optionsParamInQuery,
-                                                          final String topicParamInQuery,
-                                                          final String requestId,
-                                                          final boolean includeDescendants) {
+    public void sendResourceDataBatchRequestAsynchronously(final String topicParamInQuery,
+                                                           final ResourceDataBatchRequest
+                                                                  resourceDataBatchRequest,
+                                                           final String requestId) {
+        networkCmProxyDataService.requestResourceDataForCmHandleBatch(topicParamInQuery, resourceDataBatchRequest,
+                requestId);
 
-        return () -> networkCmProxyDataService.getResourceDataForCmHandleBatch(
-                datastoreName, cmHandleIds, resourceIdentifier, optionsParamInQuery, topicParamInQuery, requestId);
     }
-
 }
index 6d68f76..937935b 100644 (file)
@@ -20,8 +20,8 @@
 
 package org.onap.cps.ncmp.rest.controller.handlers;
 
-import java.util.List;
 import java.util.function.Supplier;
+import org.onap.cps.ncmp.api.models.ResourceDataBatchRequest;
 import org.onap.cps.spi.FetchDescendantsOption;
 
 public interface TaskManagementDefaultHandler {
@@ -46,14 +46,10 @@ public interface TaskManagementDefaultHandler {
         return NO_OBJECT_SUPPLIER;
     }
 
-    default Supplier<Object> getTaskSupplierForBulkRequest(final String datastoreName,
-                                                           final List<String> cmHandleIds,
-                                                           final String resourceIdentifier,
-                                                           final String optionsParamInQuery,
-                                                           final String topicParamInQuery,
-                                                           final String requestId,
-                                                           final boolean includeDescendant) {
-        return NO_OBJECT_SUPPLIER;
+    default void sendResourceDataBatchRequestAsynchronously(final String topicParamInQuery,
+                                                            final ResourceDataBatchRequest
+                                                                    resourceDataBatchRequest,
+                                                            final String requestId) {
     }
 
     static FetchDescendantsOption getFetchDescendantsOption(final boolean includeDescendants) {
index 5faeee6..f459ace 100755 (executable)
@@ -77,7 +77,7 @@ public class NetworkCmProxyRestExceptionHandler {
         return wrapDmiErrorResponse(HttpStatus.BAD_GATEWAY, httpClientRequestException);
     }
 
-    @ExceptionHandler({DmiRequestException.class, DataValidationException.class,
+    @ExceptionHandler({DmiRequestException.class, DataValidationException.class, OperationNotSupportedException.class,
             HttpMessageNotReadableException.class, InvalidTopicException.class, InvalidDatastoreException.class})
     public static ResponseEntity<Object> handleDmiRequestExceptions(final Exception exception) {
         return buildErrorResponse(HttpStatus.BAD_REQUEST, exception);
  * ============LICENSE_END=========================================================
  */
 
-package org.onap.cps.ncmp.api.impl.operations;
+package org.onap.cps.ncmp.rest.exceptions;
 
-import com.fasterxml.jackson.annotation.JsonValue;
-
-public enum OperationEnum {
-
-    READ("read"),
-    CREATE("create"),
-    UPDATE("update"),
-    PATCH("patch"),
-    DELETE("delete");
-    private final String value;
-
-    OperationEnum(final String value) {
-        this.value = value;
-    }
-
-    @Override
-    @JsonValue
-    public String toString() {
-        return String.valueOf(value);
+public class OperationNotSupportedException extends RuntimeException {
+    /**
+     * Instantiates a new not implemented operation exception.
+     *
+     * @param message the message
+     */
+    public OperationNotSupportedException(final String message) {
+        super(message);
     }
 }
diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/mapper/ResourceDataBatchRequestMapper.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/mapper/ResourceDataBatchRequestMapper.java
new file mode 100644 (file)
index 0000000..d045e31
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2023 Nordix Foundation
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.rest.mapper;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.NullValueCheckStrategy;
+import org.mapstruct.NullValuePropertyMappingStrategy;
+import org.onap.cps.ncmp.api.models.BatchOperationDefinition;
+import org.onap.cps.ncmp.api.models.ResourceDataBatchRequest;
+
+@Mapper(componentModel = "spring", nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS,
+        nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT)
+public interface ResourceDataBatchRequestMapper {
+
+    @Mapping(source = "operations", target = "batchOperationDefinitions")
+    ResourceDataBatchRequest toResourceDataBatchRequest(
+            org.onap.cps.ncmp.rest.model.ResourceDataBatchRequest resourceDataBatchRequest);
+
+    @Mapping(source = "targetIds", target = "cmHandleIds")
+    BatchOperationDefinition toBatchOperationDefinition(
+            org.onap.cps.ncmp.rest.model.BatchOperationDefinition batchOperationDefinition);
+}
index fb411c0..31e83aa 100644 (file)
@@ -32,11 +32,14 @@ import org.onap.cps.ncmp.api.inventory.CmHandleState
 import org.onap.cps.ncmp.api.inventory.CompositeState
 import org.onap.cps.ncmp.api.inventory.DataStoreSyncState
 import org.onap.cps.ncmp.api.inventory.LockReasonCategory
+import org.onap.cps.ncmp.rest.model.BatchOperationDefinition
 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
 import org.onap.cps.ncmp.rest.controller.handlers.NcmpCachedResourceRequestHandler
 import org.onap.cps.ncmp.rest.controller.handlers.NcmpPassthroughResourceRequestHandler
 import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor
 import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper
+import org.onap.cps.ncmp.rest.mapper.ResourceDataBatchRequestMapper
+import org.onap.cps.ncmp.rest.model.ResourceDataBatchRequest
 import org.onap.cps.ncmp.rest.util.DeprecationHelper
 import org.onap.cps.spi.FetchDescendantsOption
 import org.onap.cps.spi.model.ModuleDefinition
@@ -62,10 +65,10 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
 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
-import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.CREATE
-import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.UPDATE
-import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.PATCH
-import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.DELETE
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.PATCH
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.DELETE
 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL
 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING
 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.OPERATIONAL
@@ -97,6 +100,9 @@ class NetworkCmProxyControllerSpec extends Specification {
     @SpringBean
     CmHandleStateMapper cmHandleStateMapper = Mappers.getMapper(CmHandleStateMapper)
 
+    @SpringBean
+    ResourceDataBatchRequestMapper resourceDataBatchRequestMapper = Mappers.getMapper(ResourceDataBatchRequestMapper)
+
     @SpringBean
     CpsNcmpTaskExecutor spiedCpsTaskExecutor = Spy()
 
@@ -113,7 +119,6 @@ class NetworkCmProxyControllerSpec extends Specification {
     def ncmpBasePathV1
 
     def requestBody = '{"some-key":"some-value"}'
-    def bulkRequestBody = '["testCmHandle"]'
 
     def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC))
 
@@ -200,44 +205,69 @@ class NetworkCmProxyControllerSpec extends Specification {
             'invalid non-empty topic value in url' | 'passthrough-operational' | '&topic=1_5_*_#'
     }
 
-    def 'Get (async) bulk resource data from dmi service.'() {
-        given: 'bulk resource data url'
-            def getUrl = "$ncmpBasePathV1/batch/data/ds/${datastore.datastoreName}" +
-                    "?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=myTopic"
+    def 'Get (async) batch resource data from dmi service.'() {
+        given: 'batch resource data url'
+            def getUrl = "$ncmpBasePathV1/data?topic=my-topic-name"
+            def resourceDataBatchRequestJsonData = jsonObjectMapper.asJsonString(
+                    getResourceDataBatchRequest("read", datastore.datastoreName))
+            def expectedDmiResourceDataBatchRequest
+                    = jsonObjectMapper.convertJsonString(resourceDataBatchRequestJsonData, org.onap.cps.ncmp.api.models.ResourceDataBatchRequest.class)
         when: 'post data resource request is performed'
             def response = mvc.perform(
                     post(getUrl)
                             .contentType(MediaType.APPLICATION_JSON)
-                            .content(bulkRequestBody)
+                            .content(resourceDataBatchRequestJsonData)
             ).andReturn().response
         then: 'response status is Ok'
             response.status == HttpStatus.OK.value()
         and: 'async request id is generated'
             assert response.contentAsString.contains("requestId")
         then: 'wait a little to allow execution of service method by task executor (on separate thread)'
-            Thread.sleep(100);
+            Thread.sleep(100)
         then: 'the service has been invoked with the correct parameters '
-            1 * mockNetworkCmProxyDataService.getResourceDataForCmHandleBatch(datastore.datastoreName, ['testCmHandle'],
-                    'parent/child',
-                   '(a=1,b=2)',
-                  'myTopic',
-                 _)
+            1 * mockNetworkCmProxyDataService.requestResourceDataForCmHandleBatch('my-topic-name', expectedDmiResourceDataBatchRequest, _)
         where: 'the following data stores are used'
             datastore << [PASSTHROUGH_RUNNING, PASSTHROUGH_OPERATIONAL]
     }
 
-    def 'Get bulk resource data for non-supported #datastoreName from dmi service.'() {
-        given: 'bulk resource data url'
-            def getUrl = "$ncmpBasePathV1/batch/data/ds/ncmp-datastore:operational" +
-                    "?resourceIdentifier=parent/child&options=(a=1,b=2)&topic=myTopic"
+    def 'Get batch resource data for #scenario from dmi service.'() {
+        given: 'batch resource data url'
+            def getUrl = "$ncmpBasePathV1/data?topic=my-topic-name"
+            def resourceDataBatchRequestJsonData = jsonObjectMapper.asJsonString(
+                    getResourceDataBatchRequest(operation, datastore))
+        when: 'post data resource request is performed'
+            def response = mvc.perform(
+                    post(getUrl)
+                            .contentType(MediaType.APPLICATION_JSON)
+                            .content(resourceDataBatchRequestJsonData)
+            ).andReturn().response
+        then: 'response status is BAD_REQUEST'
+            response.status == HttpStatus.BAD_REQUEST.value()
+        where: 'the following parameters are used'
+            scenario                                            | datastore                             | operation
+            'non-supported datastoreName'                       | OPERATIONAL.datastoreName             | 'read'
+            'non-supported operation (passthrough-running)'     | PASSTHROUGH_RUNNING.datastoreName     | 'create'
+            'non-supported operation (passthrough-operational)' | PASSTHROUGH_OPERATIONAL.datastoreName | 'create'
+    }
+
+    def 'Get batch resource data when notification feature is disabled for datastore: #datastore.'() {
+        given: 'batch resource data url'
+            def getUrl = "$ncmpBasePathV1/data?topic=my-topic-name"
+            def resourceDataBatchRequestJsonData = jsonObjectMapper.asJsonString(
+                    getResourceDataBatchRequest("read", datastore.datastoreName))
+            ncmpPassthroughResourceRequestHandler.notificationFeatureEnabled = false
         when: 'post data resource request is performed'
             def response = mvc.perform(
                     post(getUrl)
                             .contentType(MediaType.APPLICATION_JSON)
-                            .content(bulkRequestBody)
+                            .content(resourceDataBatchRequestJsonData)
             ).andReturn().response
-        then: 'response status code is 501 not implemented'
-            response.status == HttpStatus.NOT_IMPLEMENTED.value()
+        then: 'response status is Ok'
+            response.status == HttpStatus.OK.value()
+        and: 'async request id is unavailable'
+            assert response.contentAsString == '{"status":"Asynchronous request is unavailable as notification feature is currently disabled."}'
+        where: 'the following data stores are used'
+            datastore << [PASSTHROUGH_RUNNING, PASSTHROUGH_OPERATIONAL]
     }
 
     def 'Query Resource Data from operational.'() {
@@ -656,5 +686,23 @@ class NetworkCmProxyControllerSpec extends Specification {
         return assertContainsAll(response, expectedContent)
     }
 
+    def getResourceDataBatchRequest(operation, datastore) {
+        def resourceDataBatchRequest = new ResourceDataBatchRequest()
+        def batchOperationDefinitions = new ArrayList()
+        batchOperationDefinitions.add(getBatchOperationDefinition(operation, datastore))
+        resourceDataBatchRequest.addOperationsItem(batchOperationDefinitions)
+    }
+
+    def getBatchOperationDefinition(operation, datastore) {
+        def batchOperationDefinition = new BatchOperationDefinition()
+        batchOperationDefinition.setOperation(operation)
+        batchOperationDefinition.setOperationId("operational-12")
+        batchOperationDefinition.setDatastore(datastore)
+        batchOperationDefinition.setOptions("some option")
+        batchOperationDefinition.setResourceIdentifier("some resource identifier")
+        batchOperationDefinition.addTargetIdsItem("some-cm-handle")
+        return batchOperationDefinition
+    }
+
 }
 
index f44d6c9..a3afc55 100644 (file)
@@ -33,6 +33,7 @@ import org.onap.cps.ncmp.rest.controller.handlers.NcmpCachedResourceRequestHandl
 import org.onap.cps.ncmp.rest.controller.handlers.NcmpPassthroughResourceRequestHandler
 import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor
 import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper
+import org.onap.cps.ncmp.rest.mapper.ResourceDataBatchRequestMapper
 import org.onap.cps.ncmp.rest.util.DeprecationHelper
 import org.onap.cps.spi.exceptions.AlreadyDefinedException
 import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch
@@ -74,6 +75,9 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification {
     @SpringBean
     CmHandleStateMapper cmHandleStateMapper = Mappers.getMapper(CmHandleStateMapper)
 
+    @SpringBean
+    ResourceDataBatchRequestMapper resourceDataBatchRequestMapper = Mappers.getMapper(ResourceDataBatchRequestMapper)
+
     @SpringBean
     CpsNcmpTaskExecutor stubbedCpsTaskExecutor = Stub()
 
index 05490d8..046c788 100644 (file)
 package org.onap.cps.ncmp.api;
 
 import java.util.Collection;
-import java.util.List;
 import java.util.Map;
-import org.onap.cps.ncmp.api.impl.operations.OperationEnum;
+import org.onap.cps.ncmp.api.impl.operations.OperationType;
 import org.onap.cps.ncmp.api.inventory.CompositeState;
 import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters;
 import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters;
 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.ncmp.api.models.ResourceDataBatchRequest;
 import org.onap.cps.spi.FetchDescendantsOption;
 import org.onap.cps.spi.model.ModuleDefinition;
 import org.onap.cps.spi.model.ModuleReference;
@@ -83,22 +83,15 @@ public interface NetworkCmProxyDataService {
                                       FetchDescendantsOption fetchDescendantsOption);
 
     /**
-     * Get resource data for given batch of cm handles using dmi.
+     * Get resource data for batch of cm handles using dmi.
      *
-     * @param datastoreName       datastore name
-     * @param cmHandleIds         cm handle identifiers
-     * @param resourceIdentifier  resource identifier
-     * @param optionsParamInQuery options query
-     * @param topicParamInQuery   topic name for (triggering) async responses
-     * @param requestId           unique requestId for async request
-     * @return {@code Object} resource data
+     * @param topicParamInQuery        topic name for (triggering) async responses
+     * @param resourceDataBatchRequest cm handle identifiers
      */
-    Object getResourceDataForCmHandleBatch(String datastoreName,
-                                       List<String> cmHandleIds,
-                                       String resourceIdentifier,
-                                       String optionsParamInQuery,
-                                       String topicParamInQuery,
-                                       String requestId);
+    void requestResourceDataForCmHandleBatch(String topicParamInQuery,
+                                         ResourceDataBatchRequest
+                                                 resourceDataBatchRequest,
+                                         String requestId);
 
 
     /**
@@ -106,14 +99,14 @@ public interface NetworkCmProxyDataService {
      *
      * @param cmHandleId         cm handle identifier
      * @param resourceIdentifier resource identifier
-     * @param operation          required operation
+     * @param operationType      required operation type
      * @param requestBody        request body to create resource
      * @param contentType        content type in body
      * @return {@code Object} return data
      */
     Object writeResourceDataPassThroughRunningForCmHandle(String cmHandleId,
                                                         String resourceIdentifier,
-                                                        OperationEnum operation,
+                                                        OperationType operationType,
                                                         String requestBody,
                                                         String contentType);
 
index e478b00..536775e 100755 (executable)
@@ -45,7 +45,7 @@ import org.onap.cps.ncmp.api.NetworkCmProxyCmHandleQueryService;
 import org.onap.cps.ncmp.api.NetworkCmProxyDataService;
 import org.onap.cps.ncmp.api.impl.events.lcm.LcmEventsCmHandleStateHandler;
 import org.onap.cps.ncmp.api.impl.operations.DmiDataOperations;
-import org.onap.cps.ncmp.api.impl.operations.OperationEnum;
+import org.onap.cps.ncmp.api.impl.operations.OperationType;
 import org.onap.cps.ncmp.api.impl.utils.CmHandleQueryConditions;
 import org.onap.cps.ncmp.api.impl.utils.InventoryQueryConditions;
 import org.onap.cps.ncmp.api.impl.utils.YangDataConverter;
@@ -63,6 +63,7 @@ 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.ncmp.api.models.ResourceDataBatchRequest;
 import org.onap.cps.spi.FetchDescendantsOption;
 import org.onap.cps.spi.exceptions.AlreadyDefinedExceptionBatch;
 import org.onap.cps.spi.exceptions.CpsException;
@@ -138,28 +139,21 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
     }
 
     @Override
-    public Object getResourceDataForCmHandleBatch(final String datastoreName,
-                                                  final List<String> cmHandleIds,
-                                                  final String resourceIdentifier,
-                                                  final String optionsParamInQuery,
-                                                  final String topicParamInQuery,
-                                                  final String requestId) {
-        final ResponseEntity<?> responseEntity = dmiDataOperations.getResourceDataFromDmi(datastoreName, cmHandleIds,
-                resourceIdentifier,
-                optionsParamInQuery,
-                topicParamInQuery,
-                requestId);
-        return responseEntity.getBody();
+    public void requestResourceDataForCmHandleBatch(final String topicParamInQuery,
+                                                final ResourceDataBatchRequest
+                                                        resourceDataBatchRequest,
+                                                final String requestId) {
+        dmiDataOperations.requestResourceDataFromDmi(topicParamInQuery, resourceDataBatchRequest, requestId);
     }
 
     @Override
     public Object writeResourceDataPassThroughRunningForCmHandle(final String cmHandleId,
                                                                  final String resourceIdentifier,
-                                                                 final OperationEnum operation,
+                                                                 final OperationType operationType,
                                                                  final String requestData,
                                                                  final String dataType) {
-        return dmiDataOperations.writeResourceDataPassThroughRunningFromDmi(cmHandleId, resourceIdentifier, operation,
-                requestData, dataType);
+        return dmiDataOperations.writeResourceDataPassThroughRunningFromDmi(cmHandleId, resourceIdentifier,
+                operationType, requestData, dataType);
     }
 
     @Override
index 9d08780..136935b 100644 (file)
@@ -24,7 +24,7 @@ package org.onap.cps.ncmp.api.impl.client;
 import lombok.AllArgsConstructor;
 import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration.DmiProperties;
 import org.onap.cps.ncmp.api.impl.exception.HttpClientRequestException;
-import org.onap.cps.ncmp.api.impl.operations.OperationEnum;
+import org.onap.cps.ncmp.api.impl.operations.OperationType;
 import org.springframework.http.HttpEntity;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.MediaType;
@@ -44,17 +44,17 @@ public class DmiRestClient {
      * Sends POST operation to DMI with json body containing module references.
      * @param dmiResourceUrl dmi resource url
      * @param requestBodyAsJsonString json data body
-     * @param operation the type of operation being executed (for error reporting only)
+     * @param operationType the type of operation being executed (for error reporting only)
      * @return response entity of type String
      */
     public ResponseEntity<Object> postOperationWithJsonData(final String dmiResourceUrl,
                                                             final String requestBodyAsJsonString,
-                                                            final OperationEnum operation) {
+                                                            final OperationType operationType) {
         final var httpEntity = new HttpEntity<>(requestBodyAsJsonString, configureHttpHeaders(new HttpHeaders()));
         try {
             return restTemplate.postForEntity(dmiResourceUrl, httpEntity, Object.class);
         } catch (final HttpStatusCodeException httpStatusCodeException) {
-            final String exceptionMessage = "Unable to " + operation.toString() + " resource data.";
+            final String exceptionMessage = "Unable to " + operationType.toString() + " resource data.";
             throw new HttpClientRequestException(exceptionMessage, httpStatusCodeException.getResponseBodyAsString(),
                     httpStatusCodeException.getRawStatusCode());
         }
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/InvalidOperationException.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/exception/InvalidOperationException.java
new file mode 100644 (file)
index 0000000..1706909
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2023 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.api.impl.exception;
+
+public class InvalidOperationException extends RuntimeException {
+    /**
+     * Instantiates a new invalid operation exception.
+     *
+     * @param message the message
+     */
+    public InvalidOperationException(final String message) {
+        super(message);
+    }
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/CmHandle.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/CmHandle.java
new file mode 100644 (file)
index 0000000..618da74
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2023 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.api.impl.operations;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.Map;
+import lombok.Builder;
+import lombok.Getter;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@Getter
+@Builder
+public class CmHandle {
+    private String id;
+
+    @JsonProperty("cmHandleProperties")
+    private Map<String, String> dmiProperties;
+
+    public static CmHandle buildCmHandleWithProperties(final String cmHandleId,
+                                                       final Map<String, String> dmiProperties) {
+        return CmHandle.builder().id(cmHandleId).dmiProperties(dmiProperties).build();
+    }
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiBatchOperation.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/DmiBatchOperation.java
new file mode 100644 (file)
index 0000000..76ad0f7
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2023 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.api.impl.operations;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Builder;
+import lombok.Getter;
+import org.onap.cps.ncmp.api.models.BatchOperationDefinition;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@Getter
+@Builder
+@JsonPropertyOrder({"operation", "operationId", "datastore", "options", "resourceIdentifier", "cmHandles"})
+public class DmiBatchOperation {
+
+    @JsonProperty("operation")
+    private OperationType operationType;
+    private String operationId;
+    private String datastore;
+    private String options;
+    private String resourceIdentifier;
+
+    private final List<CmHandle> cmHandles = new ArrayList<>();
+
+    /**
+     * Create and initialise a (outgoing) DMI batch operation.
+     *
+     * @param batchOperationDefinition batchOperationDefinition definition of incoming of batch request
+     * @return mapped dmi operation details
+     */
+    public static DmiBatchOperation buildDmiBatchRequestBodyWithoutCmHandles(
+            final BatchOperationDefinition batchOperationDefinition) {
+
+        return DmiBatchOperation.builder()
+                .operationType(OperationType.fromOperationName(batchOperationDefinition.getOperation()))
+                .operationId(batchOperationDefinition.getOperationId())
+                .datastore(DatastoreType.fromDatastoreName(batchOperationDefinition.getDatastore()).getDatastoreName())
+                .options(batchOperationDefinition.getOptions())
+                .resourceIdentifier(batchOperationDefinition.getResourceIdentifier())
+                .build();
+    }
+}
index 1a39523..3e8d40a 100644 (file)
 package org.onap.cps.ncmp.api.impl.operations;
 
 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING;
-import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.READ;
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ;
 
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
 import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.ncmp.api.impl.client.DmiRestClient;
 import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration;
 import org.onap.cps.ncmp.api.impl.executor.TaskExecutor;
-import org.onap.cps.ncmp.api.impl.utils.DmiServiceNameOrganizer;
 import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder;
+import org.onap.cps.ncmp.api.impl.utils.ResourceDataBatchRequestUtils;
 import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
 import org.onap.cps.ncmp.api.inventory.CmHandleState;
 import org.onap.cps.ncmp.api.inventory.InventoryPersistence;
+import org.onap.cps.ncmp.api.models.ResourceDataBatchRequest;
 import org.onap.cps.spi.exceptions.CpsException;
 import org.onap.cps.utils.JsonObjectMapper;
-import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Component;
+import org.springframework.util.MultiValueMap;
 
 /**
  * Operations class for DMI data.
@@ -50,7 +53,6 @@ import org.springframework.stereotype.Component;
 public class DmiDataOperations extends DmiOperations {
 
     private static final long DEFAULT_ASYNC_TASK_EXECUTOR_TIMEOUT_IN_MILLISECONDS = 30000L;
-    private static final String NO_CM_HANDLE_ID = "";
 
     public DmiDataOperations(final InventoryPersistence inventoryPersistence,
                              final JsonObjectMapper jsonObjectMapper,
@@ -88,40 +90,13 @@ public class DmiDataOperations extends DmiOperations {
         return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonRequestBody, READ);
     }
 
-    /**
-     * This method fetches the resource data by data store for given list of cm handles using dmi client.
-     *
-     * @param dataStoreName           data store name
-     * @param cmHandleIds         list of cm handles
-     * @param resourceId          resource identifier
-     * @param optionsParamInQuery options query
-     * @param topicParamInQuery   topic name for (triggering) async responses
-     * @param requestId           requestId for async responses
-     * @return {@code ResponseEntity} response entity
-     */
-    public ResponseEntity<Object> getResourceDataFromDmi(final String dataStoreName,
-                                                         final List<String> cmHandleIds,
-                                                         final String resourceId,
-                                                         final String optionsParamInQuery,
-                                                         final String topicParamInQuery,
-                                                         final String requestId) {
-        final Collection<YangModelCmHandle> yangModelCmHandles
-                = inventoryPersistence.getYangModelCmHandles(cmHandleIds);
-        final Map<String, Map<String, Map<String, String>>> dmiServiceNameCmHandlePropertiesMap =
-                DmiServiceNameOrganizer.getDmiPropertiesPerCmHandleIdPerServiceName(yangModelCmHandles);
-
-        buildBulkResourceDataRequestAndSend(dataStoreName, resourceId, optionsParamInQuery,
-                topicParamInQuery, requestId, dmiServiceNameCmHandlePropertiesMap);
-        return new ResponseEntity<>(HttpStatus.ACCEPTED);
-    }
-
     /**
      * This method fetches all the resource data from operational data store for given cm handle
      * identifier using dmi client.
      *
-     * @param dataStoreName  data store name
-     * @param cmHandleId network resource identifier
-     * @param requestId  requestId for async responses
+     * @param dataStoreName data store name
+     * @param cmHandleId    network resource identifier
+     * @param requestId     requestId for async responses
      * @return {@code ResponseEntity} response entity
      */
     public ResponseEntity<Object> getResourceDataFromDmi(final String dataStoreName,
@@ -130,12 +105,37 @@ public class DmiDataOperations extends DmiOperations {
         final YangModelCmHandle yangModelCmHandle = getYangModelCmHandle(cmHandleId);
         final String jsonRequestBody = getDmiRequestBody(READ, requestId, null, null,
                 yangModelCmHandle);
-        final String dmiResourceDataUrl = getDmiRequestUrl(dataStoreName, cmHandleId, "/", null,
-                null, yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA));
+        final String dmiResourceDataUrl = getDmiRequestUrl(dataStoreName, cmHandleId, "/",
+                null, null,
+                yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA));
         final CmHandleState cmHandleState = yangModelCmHandle.getCompositeState().getCmHandleState();
         validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState);
-        return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonRequestBody,
-                READ);
+        return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonRequestBody, READ);
+    }
+
+    /**
+     * This method requests the resource data by data store for given list of cm handles using dmi client.
+     * The data wil be returned as message on the topic specified.
+     *
+     * @param topicParamInQuery        topic name for (triggering) async responses
+     * @param resourceDataBatchRequest batch request for resource data
+     * @param requestId                requestId for as a response
+     */
+    public void requestResourceDataFromDmi(final String topicParamInQuery,
+                                           final ResourceDataBatchRequest resourceDataBatchRequest,
+                                           final String requestId)  {
+
+        final Set<String> cmHandlesIds
+                = getDistinctCmHandleIdsFromBatchRequest(resourceDataBatchRequest);
+
+        final Collection<YangModelCmHandle> yangModelCmHandles
+                = getYangModelCmHandlesInReadyState(cmHandlesIds);
+
+        final Map<String, List<DmiBatchOperation>> operationsOutPerDmiServiceName
+                = ResourceDataBatchRequestUtils.processPerOperationInBatchRequest(resourceDataBatchRequest,
+                yangModelCmHandles);
+
+        buildBatchRequestUrlAndSendToDmiService(topicParamInQuery, requestId, operationsOutPerDmiServiceName);
     }
 
     /**
@@ -143,36 +143,39 @@ public class DmiDataOperations extends DmiOperations {
      * identifier on given resource using dmi client.
      *
      * @param cmHandleId    network resource identifier
-     * @param resourceId  resource identifier
-     * @param operation   operation enum
-     * @param requestData the request data
-     * @param dataType    data type
+     * @param resourceId    resource identifier
+     * @param operationType operation enum
+     * @param requestData   the request data
+     * @param dataType      data type
      * @return {@code ResponseEntity} response entity
      */
     public ResponseEntity<Object> writeResourceDataPassThroughRunningFromDmi(final String cmHandleId,
                                                                              final String resourceId,
-                                                                             final OperationEnum operation,
+                                                                             final OperationType operationType,
                                                                              final String requestData,
                                                                              final String dataType) {
         final YangModelCmHandle yangModelCmHandle = getYangModelCmHandle(cmHandleId);
-        final String jsonRequestBody = getDmiRequestBody(operation, null, requestData, dataType,
+        final String jsonRequestBody = getDmiRequestBody(operationType, null, requestData, dataType,
                 yangModelCmHandle);
         final String dmiUrl = getDmiRequestUrl(PASSTHROUGH_RUNNING.getDatastoreName(), cmHandleId, resourceId,
                 null, null,
                 yangModelCmHandle.resolveDmiServiceName(RequiredDmiService.DATA));
         final CmHandleState cmHandleState = yangModelCmHandle.getCompositeState().getCmHandleState();
         validateIfCmHandleStateReady(yangModelCmHandle, cmHandleState);
-        return dmiRestClient.postOperationWithJsonData(dmiUrl, jsonRequestBody, operation);
+        return dmiRestClient.postOperationWithJsonData(dmiUrl, jsonRequestBody, operationType);
     }
 
     private YangModelCmHandle getYangModelCmHandle(final String cmHandleId) {
         return inventoryPersistence.getYangModelCmHandle(cmHandleId);
     }
 
-    private String getDmiRequestBody(final OperationEnum operation, final String requestId, final String requestData,
-                                     final String dataType, final YangModelCmHandle yangModelCmHandle) {
+    private String getDmiRequestBody(final OperationType operationType,
+                                     final String requestId,
+                                     final String requestData,
+                                     final String dataType,
+                                     final YangModelCmHandle yangModelCmHandle) {
         final DmiRequestBody dmiRequestBody = DmiRequestBody.builder()
-                .operation(operation)
+                .operationType(operationType)
                 .requestId(requestId)
                 .data(requestData)
                 .dataType(dataType)
@@ -181,17 +184,6 @@ public class DmiDataOperations extends DmiOperations {
         return jsonObjectMapper.asJsonString(dmiRequestBody);
     }
 
-    private String getDmiBulkRequestBody(final OperationEnum operation,
-                                         final String requestId,
-                                         final String requestData) {
-        final DmiRequestBody dmiBulkRequestBody = DmiRequestBody.builder()
-                .operation(operation)
-                .requestId(requestId)
-                .data(requestData)
-                .build();
-        return jsonObjectMapper.asJsonString(dmiBulkRequestBody);
-    }
-
     private String getDmiRequestUrl(final String dataStoreName,
                                     final String cmHandleId,
                                     final String resourceId,
@@ -204,15 +196,13 @@ public class DmiDataOperations extends DmiOperations {
                         cmHandleId));
     }
 
-    private String getDmiServiceBulkRequestUrl(final String dataStoreName,
-                                               final String resourceId,
-                                               final String optionsParamInQuery,
-                                               final String topicParamInQuery,
-                                               final String dmiServiceName) {
-        return dmiServiceUrlBuilder.getBulkRequestUrl(
-                dmiServiceUrlBuilder.populateQueryParams(resourceId, optionsParamInQuery,
-                        topicParamInQuery), dmiServiceUrlBuilder.populateUriVariables(dataStoreName, dmiServiceName,
-                        NO_CM_HANDLE_ID));
+    private String getDmiServiceBatchRequestUrl(final String dmiServiceName,
+                                                final String topicParamInQuery,
+                                                final String requestId) {
+        final MultiValueMap<String, String> batchRequestQueryParams = dmiServiceUrlBuilder
+                .getBatchRequestQueryParams(topicParamInQuery, requestId);
+        return dmiServiceUrlBuilder.getBatchRequestUrl(batchRequestQueryParams,
+                dmiServiceUrlBuilder.populateBatchUriVariables(dmiServiceName));
     }
 
     private void validateIfCmHandleStateReady(final YangModelCmHandle yangModelCmHandle,
@@ -224,31 +214,41 @@ public class DmiDataOperations extends DmiOperations {
         }
     }
 
-    private void buildBulkResourceDataRequestAndSend(final String dataStoreName,
-                                                     final String resourceId,
-                                                     final String optionsParamInQuery,
-                                                     final String topicParamInQuery,
-                                                     final String requestId,
-                                                     final Map<String, Map<String, Map<String, String>>>
-                                                             dmiServiceNameCmHandlePropertiesMap) {
-        dmiServiceNameCmHandlePropertiesMap.entrySet().parallelStream().forEach(
-                dmiServiceNameCmHandlePropertiesEntry -> {
-                    final String dmiBulkResourceDataUrl = getDmiServiceBulkRequestUrl(dataStoreName, resourceId,
-                            optionsParamInQuery, topicParamInQuery, dmiServiceNameCmHandlePropertiesEntry.getKey());
-                    final String jsonRequestBodyAsJsonString =
-                            jsonObjectMapper.asJsonString(dmiServiceNameCmHandlePropertiesEntry.getValue());
-                    final String jsonRequestBody
-                            = getDmiBulkRequestBody(READ, requestId, jsonRequestBodyAsJsonString);
-                    sendDmiResourceDataRequestToDmiService(dmiBulkResourceDataUrl, jsonRequestBody);
-                });
+    private static Set<String> getDistinctCmHandleIdsFromBatchRequest(final ResourceDataBatchRequest
+                                                                              resourceDataBatchRequest) {
+        return resourceDataBatchRequest.getBatchOperationDefinitions().stream()
+                .flatMap(batchOperationDefinition ->
+                        batchOperationDefinition.getCmHandleIds().stream()).collect(Collectors.toSet());
+    }
+
+    private Collection<YangModelCmHandle> getYangModelCmHandlesInReadyState(final Set<String> requestedCmHandleIds) {
+        // TODO Need to publish an error response to client given topic.
+        //  Code should be implemented into https://jira.onap.org/browse/CPS-1614 (
+        //  NCMP : Error handling for non-ready cm handle state)
+        return inventoryPersistence.getYangModelCmHandles(requestedCmHandleIds).stream()
+                .filter(yangModelCmHandle -> yangModelCmHandle.getCompositeState().getCmHandleState()
+                        == CmHandleState.READY).collect(Collectors.toList());
+    }
+
+    private void buildBatchRequestUrlAndSendToDmiService(final String topicParamInQuery,
+                                                         final String requestId,
+                                                         final Map<String, List<DmiBatchOperation>>
+                                                                groupsOutPerDmiServiceName) {
+
+        groupsOutPerDmiServiceName.entrySet().forEach(groupsOutPerDmiServiceNameEntry -> {
+            final String dmiServiceName = groupsOutPerDmiServiceNameEntry.getKey();
+            final List<DmiBatchOperation> dmiBatchRequestBodies = groupsOutPerDmiServiceNameEntry.getValue();
+            final String dmiBatchResourceDataUrl = getDmiServiceBatchRequestUrl(dmiServiceName, topicParamInQuery,
+                    requestId);
+            sendBatchRequestToDmiService(dmiBatchResourceDataUrl, dmiBatchRequestBodies);
+        });
     }
 
-    private void sendDmiResourceDataRequestToDmiService(final String dmiBulkResourceDataUrl,
-                                                        final String dmiResourceDataRequestAsJsonString) {
-        TaskExecutor.executeTask(() ->
-                                dmiRestClient.postOperationWithJsonData(dmiBulkResourceDataUrl,
-                                        dmiResourceDataRequestAsJsonString, READ),
-                        DEFAULT_ASYNC_TASK_EXECUTOR_TIMEOUT_IN_MILLISECONDS)
+    private void sendBatchRequestToDmiService(final String batchResourceDataUrl,
+                                              final List<DmiBatchOperation> dmiBatchRequestBodies) {
+        final String batchRequestBodiesAsJsonString = jsonObjectMapper.asJsonString(dmiBatchRequestBodies);
+        TaskExecutor.executeTask(() -> dmiRestClient.postOperationWithJsonData(batchResourceDataUrl,
+                        batchRequestBodiesAsJsonString, READ), DEFAULT_ASYNC_TASK_EXECUTOR_TIMEOUT_IN_MILLISECONDS)
                 .whenCompleteAsync(this::handleTaskCompletion);
     }
 
index 392e9c1..1bbd725 100644 (file)
@@ -66,8 +66,7 @@ public class DmiModelOperations extends DmiOperations {
      * @return module references
      */
     public List<ModuleReference> getModuleReferences(final YangModelCmHandle yangModelCmHandle) {
-        final DmiRequestBody dmiRequestBody = DmiRequestBody.builder()
-            .build();
+        final DmiRequestBody dmiRequestBody = DmiRequestBody.builder().build();
         dmiRequestBody.asDmiProperties(yangModelCmHandle.getDmiProperties());
         final ResponseEntity<Object> dmiFetchModulesResponseEntity = getResourceFromDmiWithJsonData(
             yangModelCmHandle.resolveDmiServiceName(MODEL),
@@ -109,7 +108,7 @@ public class DmiModelOperations extends DmiOperations {
                                                                   final String resourceName) {
         final String dmiResourceDataUrl = getDmiResourceUrl(dmiServiceName, cmHandle, resourceName);
         return dmiRestClient.postOperationWithJsonData(dmiResourceDataUrl, jsonRequestBody,
-                OperationEnum.READ);
+                OperationType.READ);
     }
 
     private static String getRequestBodyToFetchYangResources(final Collection<ModuleReference> newModuleReferences,
index 3aa6366..6613d3c 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2022 Nordix Foundation
+ *  Copyright (C) 2021-2023 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@ package org.onap.cps.ncmp.api.impl.operations;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -32,9 +33,11 @@ import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
 @JsonInclude(JsonInclude.Include.NON_NULL)
 @Getter
 @Builder
+@JsonPropertyOrder({"operation", "dataType", "data", "cmHandleProperties", "requestId"})
 public class DmiRequestBody {
 
-    private OperationEnum operation;
+    @JsonProperty("operation")
+    private OperationType operationType;
     private String dataType;
     private String data;
     @JsonProperty("cmHandleProperties")
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/OperationType.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/operations/OperationType.java
new file mode 100644 (file)
index 0000000..fa00d1a
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2023 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.api.impl.operations;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import lombok.Getter;
+import org.onap.cps.ncmp.api.impl.exception.InvalidOperationException;
+
+@Getter
+public enum OperationType {
+
+    READ("read"),
+    CREATE("create"),
+    UPDATE("update"),
+    PATCH("patch"),
+    DELETE("delete");
+
+    private final String operationName;
+
+    OperationType(final String operationName) {
+        this.operationName = operationName;
+    }
+
+    @Override
+    @JsonValue
+    public String toString() {
+        return String.valueOf(operationName);
+    }
+
+    private static final Map<String, OperationType> operationNameToOperationEnum = new HashMap<>();
+
+    static {
+        Arrays.stream(OperationType.values()).forEach(
+                operationType -> operationNameToOperationEnum.put(operationType.getOperationName(), operationType));
+    }
+
+    /**
+     * From operation name get operation enum type.
+     *
+     * @param operationName the operation name
+     * @return the operation enum type
+     */
+    public static OperationType fromOperationName(final String operationName) {
+        final OperationType operationType = operationNameToOperationEnum.get(operationName);
+        if (null == operationType) {
+            throw new InvalidOperationException(operationName + " is an invalid operation name");
+        }
+        return operationType;
+    }
+}
index bba8f48..5c6fa9f 100644 (file)
@@ -53,15 +53,17 @@ public class DmiServiceUrlBuilder {
     }
 
     /**
-     * This method creates the dmi service url for bulk request.
+     * This method builds batch request url.
      *
-     * @param queryParams  query param map as key,value pair
-     * @param uriVariables uri param map as key (placeholder),value pair
-     * @return {@code String} dmi service url as string
+     * @param batchRequestQueryParams  query param map as key, value pair
+     * @param batchRequestUriVariables uri param map as key (placeholder), value pair
+     * @return {@code String} batch request url as string
      */
-    public String getBulkRequestUrl(final MultiValueMap<String, String> queryParams,
-                                    final Map<String, Object> uriVariables) {
-        return getUriComponentsBuilder(getBulkResourceDataBasePathUriBuilder(), queryParams, uriVariables)
+    public String getBatchRequestUrl(final MultiValueMap<String, String> batchRequestQueryParams,
+                                     final Map<String, Object> batchRequestUriVariables) {
+        return getBatchResourceDataBasePathUriBuilder()
+                .queryParams(batchRequestQueryParams)
+                .uriVariables(batchRequestUriVariables)
                 .buildAndExpand().toUriString();
     }
 
@@ -84,12 +86,12 @@ public class DmiServiceUrlBuilder {
      *
      * @return {@code UriComponentsBuilder} dmi service url builder object
      */
-    public UriComponentsBuilder getBulkResourceDataBasePathUriBuilder() {
+    public UriComponentsBuilder getBatchResourceDataBasePathUriBuilder() {
         return UriComponentsBuilder.newInstance()
                 .path("{dmiServiceName}")
                 .pathSegment("{dmiBasePath}")
                 .pathSegment("v1")
-                .pathSegment("batch");
+                .pathSegment("data");
     }
 
     /**
@@ -113,6 +115,20 @@ public class DmiServiceUrlBuilder {
         return uriVariables;
     }
 
+    /**
+     * This method populates uri variables for batch request.
+     *
+     * @param dmiServiceName dmi service name
+     * @return {@code Map<String, Object>} uri variables as map
+     */
+    public Map<String, Object> populateBatchUriVariables(final String dmiServiceName) {
+        final Map<String, Object> uriVariables = new HashMap<>();
+        final String dmiBasePath = dmiProperties.getDmiBasePath();
+        uriVariables.put("dmiServiceName", dmiServiceName);
+        uriVariables.put("dmiBasePath", dmiBasePath);
+        return uriVariables;
+    }
+
     /**
      * This method is used to populate map from query params.
      *
@@ -134,6 +150,21 @@ public class DmiServiceUrlBuilder {
         return queryParams;
     }
 
+    /**
+     * This method is used to populate map from query params for batch request.
+     *
+     * @param topicParamInQuery topic into url param
+     * @param requestId         unique id of response for valid topic
+     * @return all valid query params as map
+     */
+    public MultiValueMap<String, String> getBatchRequestQueryParams(final String topicParamInQuery,
+                                                                    final String requestId) {
+        final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
+        getQueryParamConsumer().accept("topic", topicParamInQuery, queryParams);
+        getQueryParamConsumer().accept("requestId", requestId, queryParams);
+        return queryParams;
+    }
+
     private TriConsumer<String, String, MultiValueMap<String, String>> getQueryParamConsumer() {
         return (paramName, paramValue, paramMap) -> {
             if (Strings.isNotEmpty(paramValue)) {
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/ResourceDataBatchRequestUtils.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/utils/ResourceDataBatchRequestUtils.java
new file mode 100644 (file)
index 0000000..e4c9bfb
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2023 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.api.impl.utils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.ncmp.api.impl.operations.CmHandle;
+import org.onap.cps.ncmp.api.impl.operations.DmiBatchOperation;
+import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle;
+import org.onap.cps.ncmp.api.models.BatchOperationDefinition;
+import org.onap.cps.ncmp.api.models.ResourceDataBatchRequest;
+
+@Slf4j
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class ResourceDataBatchRequestUtils {
+
+    private static final String UNKNOWN_SERVICE_NAME = null;
+
+    /**
+     * Create a list of DMI batch operation per DMI service (name).
+     *
+     * @param resourceDataBatchRequestIn incoming batch request details for resource data
+     * @param yangModelCmHandles involved cm handles represented as YangModelCmHandle (incl. metadata)
+     *
+     * @return {@code Map<String, List<DmiBatchOperation>>} Create a list of DMI batch operation per DMI service (name).
+     */
+    public static Map<String, List<DmiBatchOperation>> processPerOperationInBatchRequest(
+            final ResourceDataBatchRequest resourceDataBatchRequestIn,
+            final Collection<YangModelCmHandle> yangModelCmHandles) {
+
+        final Map<String, Map<String, Map<String, String>>> dmiPropertiesPerCmHandleIdPerServiceName =
+                DmiServiceNameOrganizer.getDmiPropertiesPerCmHandleIdPerServiceName(yangModelCmHandles);
+
+        final Map<String, String> dmiServiceNamesPerCmHandleId =
+            getDmiServiceNamesPerCmHandleId(dmiPropertiesPerCmHandleIdPerServiceName);
+
+        final Map<String, List<DmiBatchOperation>> dmiBatchOperationsOutPerDmiServiceName = new HashMap<>();
+
+        for (final BatchOperationDefinition batchOperationDefinitionIn :
+            resourceDataBatchRequestIn.getBatchOperationDefinitions()) {
+            for (final String cmHandleId : batchOperationDefinitionIn.getCmHandleIds()) {
+                final String dmiServiceName = dmiServiceNamesPerCmHandleId.get(cmHandleId);
+                final Map<String, String> cmHandleIdProperties
+                        = dmiPropertiesPerCmHandleIdPerServiceName.get(dmiServiceName).get(cmHandleId);
+                if (cmHandleIdProperties == null) {
+                    publishErrorMessageToClientTopic(cmHandleId);
+                } else {
+                    final DmiBatchOperation dmiBatchOperationOut = getOrAddDmiBatchOperation(dmiServiceName,
+                        batchOperationDefinitionIn, dmiBatchOperationsOutPerDmiServiceName);
+                    final CmHandle cmHandle = CmHandle.buildCmHandleWithProperties(cmHandleId, cmHandleIdProperties);
+                    dmiBatchOperationOut.getCmHandles().add(cmHandle);
+                }
+            }
+        }
+        return dmiBatchOperationsOutPerDmiServiceName;
+    }
+
+    private static void publishErrorMessageToClientTopic(final String requestedCmHandleId) {
+        log.warn("cm handle {} not found", requestedCmHandleId);
+        // TODO Need to publish an error response to client given topic.
+        //  Code should be implemented into https://jira.onap.org/browse/CPS-1583 (
+        //  NCMP : Handle non-existing cm handles)
+    }
+
+    private static Map<String, String> getDmiServiceNamesPerCmHandleId(
+            final Map<String, Map<String, Map<String, String>>> dmiDmiPropertiesPerCmHandleIdPerServiceName) {
+        final Map<String, String> dmiServiceNamesPerCmHandleId = new HashMap<>();
+        for (final Map.Entry<String, Map<String, Map<String, String>>> dmiDmiPropertiesEntry
+                : dmiDmiPropertiesPerCmHandleIdPerServiceName.entrySet()) {
+            final String dmiServiceName = dmiDmiPropertiesEntry.getKey();
+            final Set<String> cmHandleIds = dmiDmiPropertiesPerCmHandleIdPerServiceName.get(dmiServiceName).keySet();
+            for (final String cmHandleId : cmHandleIds) {
+                dmiServiceNamesPerCmHandleId.put(cmHandleId, dmiServiceName);
+            }
+        }
+        dmiDmiPropertiesPerCmHandleIdPerServiceName.put(UNKNOWN_SERVICE_NAME, Collections.emptyMap());
+        return dmiServiceNamesPerCmHandleId;
+    }
+
+    private static DmiBatchOperation getOrAddDmiBatchOperation(final String dmiServiceName,
+                                                               final BatchOperationDefinition
+                                                                       batchOperationDefinitionIn,
+                                                               final Map<String, List<DmiBatchOperation>>
+                                                                       dmiBatchOperationsOutPerDmiServiceName) {
+        dmiBatchOperationsOutPerDmiServiceName
+                .computeIfAbsent(dmiServiceName, dmiServiceNameAsKey -> new ArrayList<>());
+        final List<DmiBatchOperation> dmiBatchOperationsOut
+                = dmiBatchOperationsOutPerDmiServiceName.get(dmiServiceName);
+        final boolean isNewOperation = dmiBatchOperationsOut.isEmpty()
+                || !dmiBatchOperationsOut.get(dmiBatchOperationsOut.size() - 1).getOperationId()
+                .equals(batchOperationDefinitionIn.getOperationId());
+        if (isNewOperation) {
+            final DmiBatchOperation newDmiBatchOperationOut =
+                    DmiBatchOperation.buildDmiBatchRequestBodyWithoutCmHandles(batchOperationDefinitionIn);
+            dmiBatchOperationsOut.add(newDmiBatchOperationOut);
+            return newDmiBatchOperationOut;
+        }
+        return dmiBatchOperationsOut.get(dmiBatchOperationsOut.size() - 1);
+    }
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/BatchOperationDefinition.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/BatchOperationDefinition.java
new file mode 100644 (file)
index 0000000..04075b3
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2023 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.api.models;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.ArrayList;
+import java.util.List;
+import javax.validation.Valid;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@EqualsAndHashCode
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class BatchOperationDefinition {
+
+    private String operation;
+    private String operationId;
+    private String datastore;
+    private String options;
+    private String resourceIdentifier;
+
+    @JsonProperty("targetIds")
+    @Valid
+    private List<String> cmHandleIds = new ArrayList();
+}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/ResourceDataBatchRequest.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/models/ResourceDataBatchRequest.java
new file mode 100644 (file)
index 0000000..7af107c
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2023 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.api.models;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.Collections;
+import java.util.List;
+import javax.validation.Valid;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@EqualsAndHashCode
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ResourceDataBatchRequest {
+
+    @JsonProperty("operations")
+    @Valid
+    private List<BatchOperationDefinition> batchOperationDefinitions = Collections.emptyList();
+}
index 3d8e9cb..79f7e50 100644 (file)
@@ -33,11 +33,13 @@ import org.onap.cps.ncmp.api.inventory.CompositeState
 import org.onap.cps.ncmp.api.inventory.InventoryPersistence
 import org.onap.cps.ncmp.api.inventory.LockReasonCategory
 import org.onap.cps.ncmp.api.inventory.DataStoreSyncState
+import org.onap.cps.ncmp.api.models.BatchOperationDefinition
 import org.onap.cps.ncmp.api.models.CmHandleQueryApiParameters
 import org.onap.cps.ncmp.api.models.CmHandleQueryServiceParameters
 import org.onap.cps.ncmp.api.models.ConditionApiProperties
 import org.onap.cps.ncmp.api.models.DmiPluginRegistration
 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
+import org.onap.cps.ncmp.api.models.ResourceDataBatchRequest
 import org.onap.cps.spi.exceptions.CpsException
 import org.onap.cps.spi.model.ConditionProperties
 import spock.lang.Shared
@@ -54,8 +56,8 @@ import spock.lang.Specification
 
 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL
 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING
-import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.CREATE
-import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.UPDATE
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE
 
 class NetworkCmProxyDataServiceImplSpec extends Specification {
 
@@ -133,21 +135,13 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
             response == '{dmi-response}'
     }
 
-    def 'Get bulk resource data for #datastoreName from DMI.'() {
+    def 'Get batch resource data for #datastoreName from DMI.'() {
         given: 'cpsDataService returns valid data node'
-            mockDataNode()
-        and: 'DMI returns valid response and data'
-            mockDmiDataOperations.getResourceDataFromDmi(datastoreName, ['testCmHandle'],
-                    'testResourceId', OPTIONS_PARAM,'some topic','requestId') >>
-                    new ResponseEntity<>('{dmi-bulk-response}', HttpStatus.OK)
+            def resourceDataBatchRequest = getResourceDataBatchRequest(datastoreName)
         when: 'get batch resource data is called'
-            def response = objectUnderTest.getResourceDataForCmHandleBatch(datastoreName, ['testCmHandle'],
-                    'testResourceId',
-                    OPTIONS_PARAM,
-                    'some topic',
-                    'requestId')
-        then: 'get bulk resource data returns expected response'
-            response == '{dmi-bulk-response}'
+            objectUnderTest.requestResourceDataForCmHandleBatch('some topic', resourceDataBatchRequest, 'requestId')
+        then: 'get batch resource data returns expected response'
+            1 * mockDmiDataOperations.requestResourceDataFromDmi('some topic', resourceDataBatchRequest, 'requestId')
         where: 'the following data stores are used'
             datastoreName << [PASSTHROUGH_RUNNING.datastoreName, PASSTHROUGH_OPERATIONAL.datastoreName]
     }
@@ -373,4 +367,22 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
         mockCpsDataService.getDataNodes('NCMP-Admin', 'ncmp-dmi-registry',
                 cmHandleXPath, FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> dataNode
     }
+
+    def getResourceDataBatchRequest(datastore) {
+        def resourceDataBatchRequest = new ResourceDataBatchRequest()
+        def batchOperationDefinitions = new ArrayList()
+        batchOperationDefinitions.add(getBatchOperationDefinition(datastore))
+        resourceDataBatchRequest.setBatchOperationDefinitions(batchOperationDefinitions)
+    }
+
+    def getBatchOperationDefinition(datastore) {
+        def batchOperationDefinition = new BatchOperationDefinition()
+        batchOperationDefinition.setOperation("read")
+        batchOperationDefinition.setOperationId("operational-12")
+        batchOperationDefinition.setDatastore(datastore)
+        def targetIds = new ArrayList()
+        targetIds.add("some-cm-handle")
+        batchOperationDefinition.setCmHandleIds(targetIds)
+        return batchOperationDefinition
+    }
 }
index b38ca10..6b0355e 100644 (file)
@@ -34,9 +34,9 @@ import org.springframework.web.client.HttpServerErrorException
 import org.springframework.web.client.RestTemplate
 import spock.lang.Specification
 
-import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.READ
-import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.PATCH
-import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.CREATE
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.PATCH
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE
 
 @SpringBootTest
 @ContextConfiguration(classes = [NcmpConfiguration.DmiProperties, DmiRestClient])
index 5fd4fbd..9343666 100644 (file)
@@ -24,6 +24,8 @@ package org.onap.cps.ncmp.api.impl.operations
 import com.fasterxml.jackson.databind.ObjectMapper
 import org.onap.cps.ncmp.api.impl.config.NcmpConfiguration
 import org.onap.cps.ncmp.api.impl.utils.DmiServiceUrlBuilder
+import org.onap.cps.ncmp.api.models.ResourceDataBatchRequest
+import org.onap.cps.ncmp.utils.TestUtils
 import org.onap.cps.utils.JsonObjectMapper
 import org.spockframework.spring.SpringBean
 import org.springframework.beans.factory.annotation.Autowired
@@ -35,9 +37,9 @@ import spock.lang.Shared
 
 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL
 import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING
-import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.CREATE
-import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.READ
-import static org.onap.cps.ncmp.api.impl.operations.OperationEnum.UPDATE
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.READ
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE
 
 @SpringBootTest
 @ContextConfiguration(classes = [NcmpConfiguration.DmiProperties, DmiDataOperations])
@@ -50,8 +52,6 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec {
     def NO_REQUEST_ID = null
     @Shared
     def OPTIONS_PARAM = '(a=1,b=2)'
-    @Shared
-    def expectedBulkRequestAsJson = '{"operation": "read","data": {"fe1c1f1a070c4ce5bbfda7198592a0d3": {"neType": "RadioNode"},"b8e42eed0d9541ed8d8839e8eb86b3e0": {"neType": "RadioNode"}},"requestId": "bbb67474-f705-410a-93d1-b2844e7f45fd"}'
 
     @SpringBean
     JsonObjectMapper spiedJsonObjectMapper = Spy(new JsonObjectMapper(new ObjectMapper()))
@@ -82,23 +82,26 @@ class DmiDataOperationsSpec extends DmiOperationsBaseSpec {
             'datastore running with properties'    | [yangModelCmHandleProperty] | PASSTHROUGH_RUNNING     | OPTIONS_PARAM || '{"operation":"read","cmHandleProperties":{"prop1":"val1"}}' | 'passthrough-running'     | '&options=(a=1,b=2)'
     }
 
-    def 'call get bulk resource data for #dataStore from DMI service with topic #scenario.'() {
-        given: 'collection of yang model cm Handles'
+    def 'call get batch resource data from DMI service #scenario.'() {
+        given: 'collection of yang model cm Handles and resource data batch request'
             mockYangModelCmHandleCollectionRetrieval([yangModelCmHandleProperty])
-        and: 'a positive response from DMI service when it is called with the expected parameters'
+            def resourceDataBatchRequestJsonData = TestUtils.getResourceFileContent('resourceDataBatchRequest.json')
+            def resourceDataBatchRequest = spiedJsonObjectMapper.convertJsonString(resourceDataBatchRequestJsonData, ResourceDataBatchRequest.class)
+            resourceDataBatchRequest.batchOperationDefinitions[0].cmHandleIds = [cmHandleId]
+            def requestBodyAsJsonStringArg = null
+        and: 'a positive response from DMI service when it is called with valid request parameters'
             def responseFromDmi = new ResponseEntity<Object>(HttpStatus.ACCEPTED)
-            def expectedDmiBulkResourceDataUrl = "ncmp/v1/batch/data/ds/${dataStore}?resourceIdentifier=parent/child%26options=(a=1,b=2)&topic=my-topic-name&options=(fields=schemas/schema)"
-            mockDmiRestClient.postOperationWithJsonData(expectedDmiBulkResourceDataUrl, expectedBulkRequestAsJson, READ) >> responseFromDmi
-            dmiServiceUrlBuilder.getBulkRequestUrl(_, _) >> expectedDmiBulkResourceDataUrl
-        when: 'get resource data for bulk cm handle is invoked'
-            def result = objectUnderTest.getResourceDataFromDmi( dataStore.datastoreName, [cmHandleId], resourceIdentifier,
-                    OPTIONS_PARAM,  'some-topic','requestId')
-        then: 'the result is the response from the DMI service'
-            assert result == responseFromDmi
-        where: 'the following parameters are used'
-            scenario                | dataStore
-            'datastore operational' | PASSTHROUGH_OPERATIONAL
-            'datastore running'     | PASSTHROUGH_RUNNING
+            def expectedDmiBatchResourceDataUrl = "ncmp/v1/data/topic=my-topic-name"
+            def expectedBatchRequestAsJson = '[{"operation":"read","operationId":"operational-14","datastore":"ncmp-datastore:passthrough-operational","options":"some option","resourceIdentifier":"some resource identifier","cmHandles":[{"id":"some-cm-handle","cmHandleProperties":{"prop1":"val1"}}]}]'
+            mockDmiRestClient.postOperationWithJsonData(expectedDmiBatchResourceDataUrl, _, READ.operationName) >> responseFromDmi
+            dmiServiceUrlBuilder.getBatchRequestUrl(_, _) >> expectedDmiBatchResourceDataUrl
+        when: 'get resource data for batch of cm handles are invoked'
+            objectUnderTest.requestResourceDataFromDmi('my-topic-name', resourceDataBatchRequest, 'requestId')
+        then: 'wait a little to allow execution of service method by task executor (on separate thread)'
+            Thread.sleep(100)
+        then: 'validate ncmp generated dmi request body json args'
+            1 * mockDmiRestClient.postOperationWithJsonData(expectedDmiBatchResourceDataUrl, _, READ) >> { args -> requestBodyAsJsonStringArg = args[1] }
+            assert requestBodyAsJsonStringArg == expectedBatchRequestAsJson
     }
 
     def 'call get all resource data.'() {
index ed74ad3..d1025f9 100644 (file)
@@ -36,7 +36,7 @@ import org.springframework.http.ResponseEntity
 import org.springframework.test.context.ContextConfiguration
 import spock.lang.Shared
 
-import static  org.onap.cps.ncmp.api.impl.operations.OperationEnum.READ
+import static  org.onap.cps.ncmp.api.impl.operations.OperationType.READ
 
 @SpringBootTest
 @ContextConfiguration(classes = [NcmpConfiguration.DmiProperties, DmiModelOperations])
@@ -103,9 +103,9 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec {
         then: 'the result is the response from DMI service'
             assert result == []
         where: 'the following DMI properties are used'
-            scenario               | dmiProperties       || expectedAdditionalPropertiesInRequest
-            'with properties'      | [yangModelCmHandleProperty] || '{"prop1":"val1"}'
-            'without properties'   | []                  || '{}'
+            scenario             | dmiProperties               || expectedAdditionalPropertiesInRequest
+            'with properties'    | [yangModelCmHandleProperty] || '{"prop1":"val1"}'
+            'without properties' | []                          || '{}'
     }
 
     def 'Retrieving yang resources.'() {
@@ -154,10 +154,10 @@ class DmiModelOperationsSpec extends DmiOperationsBaseSpec {
         then: 'the result is the response from DMI service'
             assert result == [mod1:'some yang source']
         where: 'the following DMI properties are used'
-            scenario                                | dmiProperties       | unknownModuleReferences || expectedAdditionalPropertiesInRequest | expectedModuleReferencesInRequest
-            'with module references and properties' | [yangModelCmHandleProperty] | newModuleReferences || '{"prop1":"val1"}' | '{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}'
-            'without module references'             | [yangModelCmHandleProperty] | []                  || '{"prop1":"val1"}' | ''
-            'without properties'                    | []                  | newModuleReferences     || '{}'                                  | '{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}'
+            scenario                                | dmiProperties               | unknownModuleReferences || expectedAdditionalPropertiesInRequest | expectedModuleReferencesInRequest
+            'with module references and properties' | [yangModelCmHandleProperty] | newModuleReferences     || '{"prop1":"val1"}'                    | '{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}'
+            'without module references'             | [yangModelCmHandleProperty] | []                      || '{"prop1":"val1"}'                    | ''
+            'without properties'                    | []                          | newModuleReferences     || '{}'                                  | '{"name":"mod1","revision":"A"},{"name":"mod2","revision":"X"}'
     }
 
     def 'Retrieving yang resources from DMI with null DMI properties.'() {
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/ResourceDataBatchRequestUtilsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/utils/ResourceDataBatchRequestUtilsSpec.groovy
new file mode 100644 (file)
index 0000000..e658749
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2023 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.api.impl.utils
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle
+import org.onap.cps.ncmp.api.inventory.CmHandleState
+import org.onap.cps.ncmp.api.inventory.CompositeStateBuilder
+import org.onap.cps.ncmp.api.models.ResourceDataBatchRequest
+import org.onap.cps.ncmp.utils.TestUtils
+import org.onap.cps.utils.JsonObjectMapper
+import org.spockframework.spring.SpringBean
+import spock.lang.Specification
+
+class ResourceDataBatchRequestUtilsSpec extends Specification {
+
+    @SpringBean
+    JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
+
+    def 'Process per operation in batch request with #serviceName.'() {
+        given: 'batch request with 3 operations'
+            def resourceDataBatchRequestJsonData = TestUtils.getResourceFileContent('resourceDataBatchRequest.json')
+            def resourceDataBatchRequest = jsonObjectMapper.convertJsonString(resourceDataBatchRequestJsonData, ResourceDataBatchRequest.class)
+        and: '4 known cm handles: ch1-dmi1, ch2-dmi1, ch3-dmi2, ch4-dmi2'
+            def yangModelCmHandles = getYangModelCmHandles()
+        when: 'Operation in batch request is processed'
+            def operationsOutPerDmiServiceName = ResourceDataBatchRequestUtils.processPerOperationInBatchRequest(resourceDataBatchRequest, yangModelCmHandles)
+        and: 'converted to a json node'
+            def dmiBatchRequestBody = jsonObjectMapper.asJsonString(operationsOutPerDmiServiceName.get(serviceName))
+            def dmiBatchRequestBodyAsJsonNode = jsonObjectMapper.convertToJsonNode(dmiBatchRequestBody).get(operationIndex)
+        then: 'it contains the correct operation details'
+            assert dmiBatchRequestBodyAsJsonNode.get('operation').asText() == 'read'
+            assert dmiBatchRequestBodyAsJsonNode.get('operationId').asText() == expectedOperationId
+            assert dmiBatchRequestBodyAsJsonNode.get('datastore').asText() == expectedDatastore
+        and: 'the correct cm handles (just for #serviceName)'
+            assert dmiBatchRequestBodyAsJsonNode.get('cmHandles').size() == expectedCmHandleIds.size()
+            expectedCmHandleIds.each {
+                dmiBatchRequestBodyAsJsonNode.get('cmHandles').toString().contains(it)
+            }
+        where: 'the following dmi service and operations are checked'
+            serviceName | operationIndex || expectedOperationId | expectedDatastore                        | expectedCmHandleIds
+            'dmi1'      | 0              || 'operational-14'    | 'ncmp-datastore:passthrough-operational' | ['ch6-dmi1']
+            'dmi1'      | 1              || 'running-12'        | 'ncmp-datastore:passthrough-running'     | ['ch1-dmi1', 'ch2-dmi1']
+            'dmi1'      | 2              || 'operational-15'    | 'ncmp-datastore:passthrough-operational' | ['ch6-dmi1']
+            'dmi2'      | 0              || 'operational-14'    | 'ncmp-datastore:passthrough-operational' | ['ch3-dmi2']
+            'dmi2'      | 1              || 'running-12'        | 'ncmp-datastore:passthrough-running'     | ['ch7-dmi2']
+            'dmi2'      | 2              || 'operational-15'    | 'ncmp-datastore:passthrough-operational' | ['ch4-dmi2']
+    }
+
+    static def getYangModelCmHandles() {
+        def dmiProperties = [new YangModelCmHandle.Property('prop', 'some DMI property')]
+        def readyState = new CompositeStateBuilder().withCmHandleState(CmHandleState.READY).withLastUpdatedTimeNow().build()
+        return [new YangModelCmHandle(id: 'ch1-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState),
+                new YangModelCmHandle(id: 'ch2-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState),
+                new YangModelCmHandle(id: 'ch6-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState),
+                new YangModelCmHandle(id: 'ch8-dmi1', dmiServiceName: 'dmi1', dmiProperties: dmiProperties, compositeState: readyState),
+                new YangModelCmHandle(id: 'ch3-dmi2', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: readyState),
+                new YangModelCmHandle(id: 'ch4-dmi2', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: readyState),
+                new YangModelCmHandle(id: 'ch7-dmi2', dmiServiceName: 'dmi2', dmiProperties: dmiProperties, compositeState: readyState),
+        ]
+    }
+}
index e1055bb..dc471e6 100644 (file)
@@ -64,13 +64,13 @@ class RestQueryParametersValidatorSpec extends Specification {
         and: 'the exception details contain the correct significant term '
             thrown.details.contains(expectedWordInDetails)
         where:
-            scenario                 | conditionName        | conditionParameters              || expectedWordInDetails
-            'unknown condition name' | 'unknownCondition'   | [['key':'value']]                  || 'conditionName'
-            'no condition name'      | ''                   | [['key':'value']]                  || 'conditionName'
-            'empty properties'       | 'validConditionName' | [[ : ]]                          || 'conditionsParameter'
-            'empty conditions'       | 'validConditionName' | [[:]]                               || 'conditionsParameter'
-            'too many properties'    | 'validConditionName' | [[key1:'value1', key2:'value2']] || 'conditionsParameter'
-            'empty key'              | 'validConditionName' | [['':'wrong']]                   || 'conditionsParameter'
+            scenario                 | conditionName        | conditionParameters                || expectedWordInDetails
+            'unknown condition name' | 'unknownCondition'   | [['key': 'value']]                 || 'conditionName'
+            'no condition name'      | ''                   | [['key': 'value']]                 || 'conditionName'
+            'empty properties'       | 'validConditionName' | [[:]]                              || 'conditionsParameter'
+            'empty conditions'       | 'validConditionName' | [[:]]                              || 'conditionsParameter'
+            'too many properties'    | 'validConditionName' | [[key1: 'value1', key2: 'value2']] || 'conditionsParameter'
+            'empty key'              | 'validConditionName' | [['': 'wrong']]                    || 'conditionsParameter'
     }
 
     def 'CM Handle Query validation: validate module name condition properties - valid query.'() {
diff --git a/cps-ncmp-service/src/test/resources/resourceDataBatchRequest.json b/cps-ncmp-service/src/test/resources/resourceDataBatchRequest.json
new file mode 100644 (file)
index 0000000..98ed39b
--- /dev/null
@@ -0,0 +1,36 @@
+{
+  "operations": [
+    {
+      "operation": "read",
+      "operationId": "operational-14",
+      "datastore": "ncmp-datastore:passthrough-operational",
+      "options": "some option",
+      "resourceIdentifier": "some resource identifier",
+      "targetIds": [
+        "ch3-dmi2",
+        "unknown-cm-handle",
+        "ch6-dmi1"
+      ]
+    },
+    {
+      "operation": "read",
+      "operationId": "running-12",
+      "datastore": "ncmp-datastore:passthrough-running",
+      "targetIds": [
+        "ch1-dmi1",
+        "ch7-dmi2",
+        "ch2-dmi1"
+      ]
+    },
+    {
+      "operation": "read",
+      "operationId": "operational-15",
+      "datastore": "ncmp-datastore:passthrough-operational",
+      "options": "some option",
+      "targetIds": [
+        "ch4-dmi2",
+        "ch6-dmi1"
+      ]
+    }
+  ]
+}
index b0615df..9181b64 100644 (file)
@@ -2,8 +2,8 @@
   "eventId": "4cb32729-85e3-44d1-aa6e-c923b9b059a5",
   "eventCorrelationId": "68f15800-8ed4-4bae-9e53-27a9e03e1911",
   "eventTime": "2023-03-28T14:29:23.876+0000",
-  "eventType": "org.onap.cps.ncmp.event.model.BulkResponseEvent",
-  "eventSchema": "urn:cps:org.onap.cps.ncmp.event.model.BulkResponseEvent",
+  "eventType": "org.onap.cps.ncmp.event.model.BatchResponseEvent",
+  "eventSchema": "urn:cps:org.onap.cps.ncmp.event.model.BatchResponseEvent",
   "eventSchemaVersion": "v1",
   "event": {
     "payload": [
index 5f290cd..851959b 100644 (file)
@@ -1,7 +1,7 @@
 {
   "request": {
     "method": "POST",
-    "urlPattern": "/dmi/v1/batch/data/ds/.*"
+    "urlPattern": "/dmi/v1/data?.*"
   },
   "response": {
     "status": 200,