2  * ============LICENSE_START=======================================================
 
   3  *  Copyright (C) 2023-2024 Nordix Foundation
 
   4  *  ================================================================================
 
   5  *  Licensed under the Apache License, Version 2.0 (the "License");
 
   6  *  you may not use this file except in compliance with the License.
 
   7  *  You may obtain a copy of the License at
 
   9  *        http://www.apache.org/licenses/LICENSE-2.0
 
  11  *  Unless required by applicable law or agreed to in writing, software
 
  12  *  distributed under the License is distributed on an "AS IS" BASIS,
 
  13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
  14  *  See the License for the specific language governing permissions and
 
  15  *  limitations under the License.
 
  17  *  SPDX-License-Identifier: Apache-2.0
 
  18  *  ============LICENSE_END=========================================================
 
  21 package org.onap.cps.ncmp.dmi.rest.stub.controller;
 
  23 import static org.onap.cps.ncmp.api.NcmpResponseStatus.SUCCESS;
 
  25 import com.fasterxml.jackson.core.JsonProcessingException;
 
  26 import com.fasterxml.jackson.databind.ObjectMapper;
 
  27 import io.cloudevents.CloudEvent;
 
  28 import io.cloudevents.core.builder.CloudEventBuilder;
 
  30 import java.util.ArrayList;
 
  31 import java.util.HashMap;
 
  32 import java.util.List;
 
  34 import java.util.UUID;
 
  35 import lombok.RequiredArgsConstructor;
 
  36 import lombok.extern.slf4j.Slf4j;
 
  37 import org.json.simple.parser.JSONParser;
 
  38 import org.json.simple.parser.ParseException;
 
  39 import org.onap.cps.ncmp.api.impl.utils.EventDateTimeFormatter;
 
  40 import org.onap.cps.ncmp.dmi.rest.stub.model.data.operational.DataOperationRequest;
 
  41 import org.onap.cps.ncmp.dmi.rest.stub.model.data.operational.DmiDataOperationRequest;
 
  42 import org.onap.cps.ncmp.dmi.rest.stub.model.data.operational.DmiOperationCmHandle;
 
  43 import org.onap.cps.ncmp.dmi.rest.stub.utils.ResourceFileReaderUtil;
 
  44 import org.onap.cps.ncmp.events.async1_0_0.Data;
 
  45 import org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent;
 
  46 import org.onap.cps.ncmp.events.async1_0_0.Response;
 
  47 import org.springframework.beans.factory.annotation.Value;
 
  48 import org.springframework.context.ApplicationContext;
 
  49 import org.springframework.core.io.Resource;
 
  50 import org.springframework.core.io.ResourceLoader;
 
  51 import org.springframework.http.HttpStatus;
 
  52 import org.springframework.http.ResponseEntity;
 
  53 import org.springframework.kafka.core.KafkaTemplate;
 
  54 import org.springframework.web.bind.annotation.DeleteMapping;
 
  55 import org.springframework.web.bind.annotation.GetMapping;
 
  56 import org.springframework.web.bind.annotation.PathVariable;
 
  57 import org.springframework.web.bind.annotation.PostMapping;
 
  58 import org.springframework.web.bind.annotation.PutMapping;
 
  59 import org.springframework.web.bind.annotation.RequestBody;
 
  60 import org.springframework.web.bind.annotation.RequestHeader;
 
  61 import org.springframework.web.bind.annotation.RequestMapping;
 
  62 import org.springframework.web.bind.annotation.RequestParam;
 
  63 import org.springframework.web.bind.annotation.RestController;
 
  66 @RequestMapping("${rest.api.dmi-stub-base-path}")
 
  67 @RequiredArgsConstructor
 
  69 public class DmiRestStubController {
 
  71     private static final String DEFAULT_TAG = "tagD";
 
  72     private static final String dataOperationEventType = "org.onap.cps.ncmp.events.async1_0_0.DataOperationEvent";
 
  73     private static final Map<String, String> moduleSetTagPerCmHandleId = new HashMap<>();
 
  74     private final KafkaTemplate<String, CloudEvent> cloudEventKafkaTemplate;
 
  75     private final ObjectMapper objectMapper;
 
  76     private final ApplicationContext applicationContext;
 
  77     @Value("${app.ncmp.async-m2m.topic}")
 
  78     private String ncmpAsyncM2mTopic;
 
  79     @Value("${delay.module-references-delay-ms}")
 
  80     private long moduleReferencesDelayMs;
 
  81     @Value("${delay.module-resources-delay-ms}")
 
  82     private long moduleResourcesDelayMs;
 
  83     @Value("${delay.data-for-cm-handle-delay-ms}")
 
  84     private long dataForCmHandleDelayMs;
 
  87      * This code defines a REST API endpoint for adding new the module set tag mapping. The endpoint receives the
 
  88      * cmHandleId and moduleSetTag as request body and add into moduleSetTagPerCmHandleId map with the provided
 
  91      * @param requestBody map of cmHandleId and moduleSetTag
 
  92      * @return a ResponseEntity object containing the updated moduleSetTagPerCmHandleId map as the response body
 
  94     @PostMapping("/v1/tagMapping")
 
  95     public ResponseEntity<Map<String, String>> addTagForMapping(@RequestBody final Map<String, String> requestBody) {
 
  96         moduleSetTagPerCmHandleId.putAll(requestBody);
 
  97         return new ResponseEntity<>(requestBody, HttpStatus.CREATED);
 
 101      * This code defines a GET endpoint of  module set tag mapping.
 
 103      * @return The map represents the module set tag mapping.
 
 105     @GetMapping("/v1/tagMapping")
 
 106     public ResponseEntity<Map<String, String>> getTagMapping() {
 
 107         return ResponseEntity.ok(moduleSetTagPerCmHandleId);
 
 111      * This code defines a GET endpoint of  module set tag by cm handle ID.
 
 113      * @return The map represents the module set tag mapping filtered by cm handle ID.
 
 115     @GetMapping("/v1/tagMapping/ch/{cmHandleId}")
 
 116     public ResponseEntity<String> getTagMappingByCmHandleId(@PathVariable final String cmHandleId) {
 
 117         return ResponseEntity.ok(moduleSetTagPerCmHandleId.get(cmHandleId));
 
 121      * This code defines a REST API endpoint for updating the module set tag mapping. The endpoint receives the
 
 122      * cmHandleId and moduleSetTag as request body and updates the moduleSetTagPerCmHandleId map with the provided
 
 125      * @param requestBody map of cmHandleId and moduleSetTag
 
 126      * @return a ResponseEntity object containing the updated moduleSetTagPerCmHandleId map as the response body
 
 129     @PutMapping("/v1/tagMapping")
 
 130     public ResponseEntity<Map<String, String>> updateTagMapping(@RequestBody final Map<String, String> requestBody) {
 
 131         moduleSetTagPerCmHandleId.putAll(requestBody);
 
 132         return ResponseEntity.noContent().build();
 
 136      * It contains a method to delete an entry from the moduleSetTagPerCmHandleId map.
 
 137      * The method takes a cmHandleId as a parameter and removes the corresponding entry from the map.
 
 139      * @return a ResponseEntity containing the updated map.
 
 141     @DeleteMapping("/v1/tagMapping/ch/{cmHandleId}")
 
 142     public ResponseEntity<String> deleteTagMappingByCmHandleId(@PathVariable final String cmHandleId) {
 
 143         moduleSetTagPerCmHandleId.remove(cmHandleId);
 
 144         return ResponseEntity.ok(String.format("Mapping of %s is deleted successfully", cmHandleId));
 
 148      * Get all modules for given cm handle.
 
 150      * @param cmHandleId              The identifier for a network function, network element, subnetwork,
 
 151      *                                or any other cm object by managed Network CM Proxy
 
 152      * @param moduleReferencesRequest module references request body
 
 153      * @return ResponseEntity response entity having module response as json string.
 
 155     @PostMapping("/v1/ch/{cmHandleId}/modules")
 
 156     public ResponseEntity<String> getModuleReferences(@PathVariable("cmHandleId") final String cmHandleId,
 
 157                                                       @RequestBody final Object moduleReferencesRequest) {
 
 158         delay(moduleReferencesDelayMs);
 
 160             log.info("Incoming DMI request body: {}",
 
 161                     objectMapper.writeValueAsString(moduleReferencesRequest));
 
 162         } catch (final JsonProcessingException jsonProcessingException) {
 
 163             log.info("Unable to parse dmi data operation request to json string");
 
 165         final String moduleResponseContent = getModuleResourceResponse(cmHandleId,
 
 166                 "ModuleResponse.json");
 
 167         log.info("cm handle: {} requested for modules", cmHandleId);
 
 168         return ResponseEntity.ok(moduleResponseContent);
 
 172      * Retrieves module resources for a given cmHandleId.
 
 174      * @param cmHandleId                 The identifier for a network function, network element, subnetwork,
 
 175      *                                   or any other cm object by managed Network CM Proxy
 
 176      * @param moduleResourcesReadRequest module resources read request body
 
 177      * @return ResponseEntity response entity having module resources response as json string.
 
 179     @PostMapping("/v1/ch/{cmHandleId}/moduleResources")
 
 180     public ResponseEntity<String> retrieveModuleResources(
 
 181             @PathVariable("cmHandleId") final String cmHandleId,
 
 182             @RequestBody final Object moduleResourcesReadRequest) {
 
 183         delay(moduleResourcesDelayMs);
 
 184         final String moduleResourcesResponseContent = getModuleResourceResponse(cmHandleId,
 
 185                 "ModuleResourcesResponse.json");
 
 186         log.info("cm handle: {} requested for modules resources", cmHandleId);
 
 187         return ResponseEntity.ok(moduleResourcesResponseContent);
 
 191      * Create resource data from passthrough operational or running for a cm handle.
 
 193      * @param cmHandleId              The identifier for a network function, network element, subnetwork,
 
 194      *                                or any other cm object by managed Network CM Proxy
 
 195      * @param datastoreName           datastore name
 
 196      * @param resourceIdentifier      resource identifier
 
 197      * @param options                 options
 
 198      * @param topic                   client given topic name
 
 199      * @return (@ code ResponseEntity) response entity
 
 201     @PostMapping("/v1/ch/{cmHandleId}/data/ds/{datastoreName}")
 
 202     public ResponseEntity<String> getResourceDataForCmHandle(
 
 203             @PathVariable("cmHandleId") final String cmHandleId,
 
 204             @PathVariable("datastoreName") final String datastoreName,
 
 205             @RequestParam(value = "resourceIdentifier") final String resourceIdentifier,
 
 206             @RequestParam(value = "options", required = false) final String options,
 
 207             @RequestParam(value = "topic", required = false) final String topic,
 
 208             @RequestHeader(value = "Authorization", required = false) final String authorization,
 
 209             @RequestBody final String requestBody) {
 
 210         log.info("DMI AUTH HEADER: {}", authorization);
 
 211         delay(dataForCmHandleDelayMs);
 
 212         log.info("Logging request body {}", requestBody);
 
 214         final String sampleJson = ResourceFileReaderUtil.getResourceFileContent(applicationContext.getResource(
 
 215                 ResourceLoader.CLASSPATH_URL_PREFIX + "data/operational/ietf-network-topology-sample-rfc8345.json"));
 
 216         return ResponseEntity.ok(sampleJson);
 
 220      * This method is not implemented for ONAP DMI plugin.
 
 222      * @param topic                   client given topic name
 
 223      * @param requestId               requestId generated by NCMP as an ack for client
 
 224      * @param dmiDataOperationRequest list of operation details
 
 225      * @return (@ code ResponseEntity) response entity
 
 227     @PostMapping("/v1/data")
 
 228     public ResponseEntity<Void> getResourceDataForCmHandleDataOperation(
 
 229             @RequestParam(value = "topic") final String topic,
 
 230             @RequestParam(value = "requestId") final String requestId,
 
 231             @RequestBody final DmiDataOperationRequest dmiDataOperationRequest) {
 
 232         delay(dataForCmHandleDelayMs);
 
 234             log.info("Request received from the NCMP to DMI Plugin: {}",
 
 235                     objectMapper.writeValueAsString(dmiDataOperationRequest));
 
 236         } catch (final JsonProcessingException jsonProcessingException) {
 
 237             log.info("Unable to process dmi data operation request to json string");
 
 239         dmiDataOperationRequest.getOperations().forEach(dmiDataOperation -> {
 
 240             final DataOperationEvent dataOperationEvent = getDataOperationEvent(dmiDataOperation);
 
 241             dmiDataOperation.getCmHandles().forEach(dmiOperationCmHandle -> {
 
 242                 log.info("Module Set Tag received: {}", dmiOperationCmHandle.getModuleSetTag());
 
 243                 dataOperationEvent.getData().getResponses().get(0).setIds(List.of(dmiOperationCmHandle.getId()));
 
 244                 final CloudEvent cloudEvent = buildAndGetCloudEvent(topic, requestId, dataOperationEvent);
 
 245                 cloudEventKafkaTemplate.send(ncmpAsyncM2mTopic, UUID.randomUUID().toString(), cloudEvent);
 
 248         return new ResponseEntity<>(HttpStatus.ACCEPTED);
 
 251     private CloudEvent buildAndGetCloudEvent(final String topic, final String requestId,
 
 252                                              final DataOperationEvent dataOperationEvent) {
 
 253         CloudEvent cloudEvent = null;
 
 255             cloudEvent = CloudEventBuilder.v1()
 
 256                     .withId(UUID.randomUUID().toString())
 
 257                     .withSource(URI.create("DMI"))
 
 258                     .withType(dataOperationEventType)
 
 259                     .withDataSchema(URI.create("urn:cps:" + dataOperationEventType + ":1.0.0"))
 
 260                     .withTime(EventDateTimeFormatter.toIsoOffsetDateTime(
 
 261                             EventDateTimeFormatter.getCurrentIsoFormattedDateTime()))
 
 262                     .withData(objectMapper.writeValueAsBytes(dataOperationEvent))
 
 263                     .withExtension("destination", topic)
 
 264                     .withExtension("correlationid", requestId)
 
 266         } catch (final JsonProcessingException jsonProcessingException) {
 
 267             log.error("Unable to parse event into bytes. cause : {}", jsonProcessingException.getMessage());
 
 272     private DataOperationEvent getDataOperationEvent(final DataOperationRequest dataOperationRequest) {
 
 273         final Response response = new Response();
 
 275         response.setOperationId(dataOperationRequest.getOperationId());
 
 276         response.setStatusCode(SUCCESS.getCode());
 
 277         response.setStatusMessage(SUCCESS.getMessage());
 
 278         response.setIds(dataOperationRequest.getCmHandles().stream().map(DmiOperationCmHandle::getId).toList());
 
 279         response.setResourceIdentifier(dataOperationRequest.getResourceIdentifier());
 
 280         response.setOptions(dataOperationRequest.getOptions());
 
 281         final String ietfNetworkTopologySample = ResourceFileReaderUtil
 
 282                 .getResourceFileContent(applicationContext.getResource(
 
 283                         ResourceLoader.CLASSPATH_URL_PREFIX
 
 284                                 + "data/operational/ietf-network-topology-sample-rfc8345.json"));
 
 285         final JSONParser jsonParser = new JSONParser();
 
 287             response.setResult(jsonParser.parse(ietfNetworkTopologySample));
 
 288         } catch (final ParseException parseException) {
 
 289             log.error("Unable to parse event result as json object. cause : {}", parseException.getMessage());
 
 291         final List<Response> responseList = new ArrayList<>(1);
 
 292         responseList.add(response);
 
 293         final Data data = new Data();
 
 294         data.setResponses(responseList);
 
 295         final DataOperationEvent dataOperationEvent = new DataOperationEvent();
 
 296         dataOperationEvent.setData(data);
 
 297         return dataOperationEvent;
 
 300     private String getModuleResourceResponse(final String cmHandleId, final String moduleResponseType) {
 
 301         if (moduleSetTagPerCmHandleId.isEmpty()) {
 
 302             log.info("Using default module responses of type ietfYang");
 
 303             return ResourceFileReaderUtil.getResourceFileContent(applicationContext.getResource(
 
 304                     ResourceLoader.CLASSPATH_URL_PREFIX
 
 305                             + String.format("module/ietfYang-%s", moduleResponseType)));
 
 307         final String moduleSetTag = moduleSetTagPerCmHandleId.getOrDefault(cmHandleId, DEFAULT_TAG);
 
 308         final String moduleResponseFilePath = String.format("module/%s-%s", moduleSetTag, moduleResponseType);
 
 309         final Resource moduleResponseResource = applicationContext.getResource(
 
 310                 ResourceLoader.CLASSPATH_URL_PREFIX + moduleResponseFilePath);
 
 311         log.info("Using module responses from : {}", moduleResponseFilePath);
 
 312         return ResourceFileReaderUtil.getResourceFileContent(moduleResponseResource);
 
 315     private void delay(final long milliseconds) {
 
 317             Thread.sleep(milliseconds);
 
 318         } catch (final InterruptedException e) {
 
 319             log.error("Thread sleep interrupted: {}", e.getMessage());
 
 320             Thread.currentThread().interrupt();