Subscription Create Response Handling Dmi Part

- Consume suscription event (payload) with kafka message key
  and kafka timestamp from ncmp, and extract cm handle ids.
- Generate subscription event response (payload) and revert it
  back to ncmp to specified topic with the corresponding
  kafka message key.
- Configure kafka consumer properties and dmi service name.

Issue-ID: CPS-1492
Change-Id: I87de30c00e0f93e350ce6f9fd6079504952da09b
Signed-off-by: halil.cakal <halil.cakal@est.tech>
diff --git a/src/main/java/org/onap/cps/ncmp/dmi/notifications/avc/SubscriptionEventConsumer.java b/src/main/java/org/onap/cps/ncmp/dmi/notifications/avc/SubscriptionEventConsumer.java
new file mode 100644
index 0000000..2094979
--- /dev/null
+++ b/src/main/java/org/onap/cps/ncmp/dmi/notifications/avc/SubscriptionEventConsumer.java
@@ -0,0 +1,110 @@
+/*
+ * ============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.dmi.notifications.avc;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.ncmp.dmi.service.model.SubscriptionEventResponse;
+import org.onap.cps.ncmp.dmi.service.model.SubscriptionEventResponseStatus;
+import org.onap.cps.ncmp.event.model.SubscriptionEvent;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.kafka.annotation.KafkaListener;
+import org.springframework.kafka.core.KafkaTemplate;
+import org.springframework.kafka.support.KafkaHeaders;
+import org.springframework.messaging.handler.annotation.Header;
+import org.springframework.messaging.handler.annotation.Payload;
+import org.springframework.stereotype.Component;
+
+@Component
+@Slf4j
+@RequiredArgsConstructor
+public class SubscriptionEventConsumer {
+
+    @Value("${app.dmi.avc.subscription-response-topic}")
+    private String cmAvcSubscriptionResponseTopic;
+    @Value("${dmi.service.name}")
+    private String dmiName;
+    private final KafkaTemplate<String, SubscriptionEventResponse> kafkaTemplate;
+
+    /**
+     * Consume the specified event.
+     *
+     * @param subscriptionEvent the event to be consumed
+     */
+    @KafkaListener(topics = "${app.dmi.avc.subscription-topic}",
+            properties = {"spring.json.value.default.type=org.onap.cps.ncmp.event.model.SubscriptionEvent"})
+    public void consumeSubscriptionEvent(@Payload final SubscriptionEvent subscriptionEvent,
+                                         @Header(KafkaHeaders.RECEIVED_MESSAGE_KEY) final String eventKey,
+                                         @Header(KafkaHeaders.RECEIVED_TIMESTAMP) final String timeStampReceived) {
+        final Date dateAndTimeReceived = new Date(Long.parseLong(timeStampReceived));
+        final String subscriptionName = subscriptionEvent.getEvent().getSubscription().getName();
+        log.info("Subscription for SubscriptionName {} is received at {} by dmi plugin.", subscriptionName,
+                dateAndTimeReceived);
+        sendSubscriptionResponseMessage(eventKey, formSubscriptionEventResponse(subscriptionEvent));
+    }
+
+    /**
+     * Sends message to the configured topic.
+     *
+     * @param eventKey is the kafka message key
+     * @param subscriptionEventResponse is the payload of the kafka message
+     */
+    public void sendSubscriptionResponseMessage(final String eventKey,
+                                                final SubscriptionEventResponse subscriptionEventResponse) {
+        kafkaTemplate.send(cmAvcSubscriptionResponseTopic, eventKey, subscriptionEventResponse);
+    }
+
+    private SubscriptionEventResponse formSubscriptionEventResponse(final SubscriptionEvent subscriptionEvent) {
+        final SubscriptionEventResponse subscriptionEventResponse = new SubscriptionEventResponse();
+        subscriptionEventResponse.setClientId(subscriptionEvent.getEvent().getSubscription().getClientID());
+        subscriptionEventResponse.setSubscriptionName(subscriptionEvent.getEvent().getSubscription().getName());
+        subscriptionEventResponse.setDmiName(dmiName);
+        final List<Object> cmHandleIdToCmHandlePropertyMap = subscriptionEvent.getEvent()
+                .getPredicates()
+                .getTargets();
+        subscriptionEventResponse
+                .setCmHandleIdToStatus(populateCmHandleIdToStatus(extractCmHandleIds(cmHandleIdToCmHandlePropertyMap)));
+        return subscriptionEventResponse;
+    }
+
+    private Set<String> extractCmHandleIds(final List<Object> cmHandleIdTocmHandlePropertyMap) {
+        final Set<String> cmHandleIds = new HashSet<>();
+        for (final Object obj: cmHandleIdTocmHandlePropertyMap) {
+            final Map<String, Object> cmHandleIdToPropertiesMap = (Map<String, Object>) obj;
+            cmHandleIds.addAll(cmHandleIdToPropertiesMap.keySet());
+        }
+        return cmHandleIds;
+    }
+
+    private Map<String, SubscriptionEventResponseStatus> populateCmHandleIdToStatus(final Set<String> cmHandleIds) {
+        final Map<String, SubscriptionEventResponseStatus> result = new HashMap<>();
+        for (final String cmHandleId : cmHandleIds) {
+            result.put(cmHandleId, SubscriptionEventResponseStatus.ACCEPTED);
+        }
+        return result;
+    }
+}
diff --git a/src/main/java/org/onap/cps/ncmp/dmi/service/model/SubscriptionEventResponse.java b/src/main/java/org/onap/cps/ncmp/dmi/service/model/SubscriptionEventResponse.java
new file mode 100644
index 0000000..7666e54
--- /dev/null
+++ b/src/main/java/org/onap/cps/ncmp/dmi/service/model/SubscriptionEventResponse.java
@@ -0,0 +1,36 @@
+/*
+ * ============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.dmi.service.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import java.util.Map;
+import lombok.Getter;
+import lombok.Setter;
+
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@Getter
+@Setter
+public class SubscriptionEventResponse {
+    private String clientId;
+    private String subscriptionName;
+    private String dmiName;
+    private Map<String, SubscriptionEventResponseStatus> cmHandleIdToStatus;
+}
diff --git a/src/main/java/org/onap/cps/ncmp/dmi/service/model/SubscriptionEventResponseStatus.java b/src/main/java/org/onap/cps/ncmp/dmi/service/model/SubscriptionEventResponseStatus.java
new file mode 100644
index 0000000..8987dda
--- /dev/null
+++ b/src/main/java/org/onap/cps/ncmp/dmi/service/model/SubscriptionEventResponseStatus.java
@@ -0,0 +1,27 @@
+/*
+ * ============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.dmi.service.model;
+
+public enum SubscriptionEventResponseStatus {
+    ACCEPTED,
+    REJECTED,
+    PENDING
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 6ad9d58..0400143 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -1,5 +1,5 @@
 #  ============LICENSE_START=======================================================
-#  Copyright (C) 2021-2022 Nordix Foundation
+#  Copyright (C) 2021-2023 Nordix Foundation
 #  Modifications Copyright (C) 2021 Bell Canada.
 #  ================================================================================
 #  Licensed under the Apache License, Version 2.0 (the "License");
@@ -23,6 +23,7 @@
 dmi:
   service:
     url: ${DMI_SERVICE_URL}
+    name: ${DMI_SERVICE_NAME:ncmp-dmi-plugin}
 
 rest:
   api:
@@ -52,11 +53,23 @@
       key-serializer: org.apache.kafka.common.serialization.StringSerializer
       value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
       client-id: ncmp-dmi-plugin
+    consumer:
+      group-id: ${NCMP_CONSUMER_GROUP_ID:ncmp-group}
+      key-deserializer: org.springframework.kafka.support.serializer.ErrorHandlingDeserializer
+      value-deserializer: org.springframework.kafka.support.serializer.ErrorHandlingDeserializer
+      properties:
+        spring.deserializer.key.delegate.class: org.apache.kafka.common.serialization.StringDeserializer
+        spring.deserializer.value.delegate.class: org.springframework.kafka.support.serializer.JsonDeserializer
+        spring.json.use.type.headers: false
 
 app:
   ncmp:
     async:
       topic: ${NCMP_ASYNC_M2M_TOPIC:ncmp-async-m2m}
+  dmi:
+    avc:
+      subscription-topic: ${DMI_CM_AVC_SUBSCRIPTION:ncmp-dmi-cm-avc-subscription-${dmi.service.name}}
+      subscription-response-topic: ${DMI_CM_AVC_SUBSCRIPTION_RESPONSE:dmi-ncmp-cm-avc-subscription}
 
 notification:
   async:
diff --git a/src/test/groovy/org/onap/cps/ncmp/dmi/notifications/avc/SubscriptionEventConsumerSpec.groovy b/src/test/groovy/org/onap/cps/ncmp/dmi/notifications/avc/SubscriptionEventConsumerSpec.groovy
new file mode 100644
index 0000000..65567ef
--- /dev/null
+++ b/src/test/groovy/org/onap/cps/ncmp/dmi/notifications/avc/SubscriptionEventConsumerSpec.groovy
@@ -0,0 +1,109 @@
+/*
+ * ============LICENSE_START=======================================================
+ *  Copyright (C) 2023 Nordix Foundation
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.ncmp.dmi.notifications.avc
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import org.apache.kafka.clients.consumer.KafkaConsumer
+import org.onap.cps.ncmp.dmi.TestUtils
+import org.onap.cps.ncmp.dmi.api.kafka.MessagingBaseSpec
+import org.onap.cps.ncmp.dmi.service.model.SubscriptionEventResponse
+import org.onap.cps.ncmp.dmi.service.model.SubscriptionEventResponseStatus
+import org.onap.cps.ncmp.event.model.SubscriptionEvent
+import org.spockframework.spring.SpringBean
+import org.springframework.boot.test.context.SpringBootTest
+import org.springframework.test.annotation.DirtiesContext
+import org.testcontainers.spock.Testcontainers
+
+import java.time.Duration
+
+@SpringBootTest(classes = [SubscriptionEventConsumer])
+@Testcontainers
+@DirtiesContext
+class SubscriptionEventConsumerSpec extends MessagingBaseSpec {
+
+    def kafkaConsumer = new KafkaConsumer<>(consumerConfigProperties('ncmp-group'))
+
+    def objectMapper = new ObjectMapper()
+    def testTopic = 'dmi-ncmp-cm-avc-subscription'
+
+    @SpringBean
+    SubscriptionEventConsumer objectUnderTest = new SubscriptionEventConsumer(kafkaTemplate)
+
+    def 'Sends subscription event response successfully.'() {
+        given: 'an subscription event response'
+            def responseStatus = SubscriptionEventResponseStatus.ACCEPTED
+            def cmHandleIdToStatusMap = ['CmHandle1':responseStatus, 'CmHandle2':responseStatus]
+            def subscriptionEventResponse = new SubscriptionEventResponse(subscriptionName: 'cm-subscription-001',
+                clientId: 'SCO-9989752', dmiName: 'ncmp-dmi-plugin', cmHandleIdToStatus: cmHandleIdToStatusMap)
+            objectUnderTest.cmAvcSubscriptionResponseTopic = testTopic
+        and: 'consumer has a subscription'
+            kafkaConsumer.subscribe([testTopic] as List<String>)
+        when: 'an event is published'
+            def eventKey = UUID.randomUUID().toString()
+            objectUnderTest.sendSubscriptionResponseMessage(eventKey, subscriptionEventResponse)
+        and: 'topic is polled'
+            def records = kafkaConsumer.poll(Duration.ofMillis(1500))
+        then: 'poll returns one record'
+            assert records.size() == 1
+            def record = records.iterator().next()
+        and: 'the record value matches the expected event value'
+            def expectedValue = objectMapper.writeValueAsString(subscriptionEventResponse)
+            assert expectedValue == record.value
+            assert eventKey == record.key
+    }
+
+    def 'Consume valid message.'() {
+        given: 'an event'
+            def jsonData = TestUtils.getResourceFileContent('avcSubscriptionCreationEvent.json')
+            def testEventSent = objectMapper.readValue(jsonData, SubscriptionEvent.class)
+            objectUnderTest.cmAvcSubscriptionResponseTopic = testTopic
+        when: 'the valid event is consumed'
+            def eventKey = UUID.randomUUID().toString()
+            def timeStampReceived = '1679521929511'
+            objectUnderTest.consumeSubscriptionEvent(testEventSent, eventKey, timeStampReceived)
+        then: 'no exception is thrown'
+            noExceptionThrown()
+    }
+
+    def 'Extract cm handle ids from cm handle id to cm handle property map successfully.'() {
+        given: 'a list of cm handle id to cm handle property map'
+            def cmHandleIdToPropertyMap =
+                ['CmHandle1':['prop-x':'prop-valuex'], 'CmHandle2':['prop-y':'prop-valuey']]
+            def listOfCmHandleIdToPropertyMap =
+                [cmHandleIdToPropertyMap]
+        when: 'extract the cm handle ids'
+            def result = objectUnderTest.extractCmHandleIds(listOfCmHandleIdToPropertyMap)
+        then: 'cm handle ids are extracted as expected'
+            def expectedCmHandleIds = ['CmHandle1', 'CmHandle2'] as Set
+            assert expectedCmHandleIds == result
+    }
+
+    def 'Populate cm handle id to status map successfully.'() {
+        given: 'a set of cm handle id'
+            def cmHandleIds = ['CmHandle1', 'CmHandle2'] as Set
+            def responseStatus = SubscriptionEventResponseStatus.ACCEPTED
+        when: 'populate cm handle id to status map'
+            def result = objectUnderTest.populateCmHandleIdToStatus(cmHandleIds)
+        then: 'cm handle id to status map populated as expected'
+            def expectedMap = ['CmHandle1':responseStatus,'CmHandle2':responseStatus]
+            expectedMap == result
+    }
+}
\ No newline at end of file
diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml
index 9ed37a7..d2aafbd 100644
--- a/src/test/resources/application.yml
+++ b/src/test/resources/application.yml
@@ -1,5 +1,5 @@
 # ============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.
@@ -43,6 +43,9 @@
 dmi:
   service:
     url: some url for the dmi service
+  avc:
+    subscription-topic: ncmp-dmi-cm-avc-subscription
+    subscription-response-topic: dmi-ncmp-cm-avc-subscription
 
 spring:
   application:
diff --git a/src/test/resources/avcSubscriptionCreationEvent.json b/src/test/resources/avcSubscriptionCreationEvent.json
new file mode 100644
index 0000000..d52a91e
--- /dev/null
+++ b/src/test/resources/avcSubscriptionCreationEvent.json
@@ -0,0 +1,45 @@
+{
+  "version": "1.0",
+  "eventType": "CREATE",
+  "event": {
+    "subscription": {
+      "clientID": "SCO-9989752",
+      "name": "cm-subscription-001"
+    },
+    "dataType": {
+      "dataspace": "ALL",
+      "dataCategory": "CM",
+      "dataProvider": "CM-SERVICE",
+      "schemaName": "org.onap.ncmp:cm-network-avc-event.rfc8641",
+      "schemaVersion": "1.0"
+    },
+    "predicates": {
+      "targets": [
+        {
+          "CMHandle1": {
+            "cmhandle-properties": {
+              "prop1": "prop-value"
+            }
+          }
+        },
+        {
+          "CMHandle2": {
+            "cmhandle-properties": {
+              "prop-x": "prop-valuex",
+              "prop-p": "prop-valuep"
+            }
+          }
+        },
+        {
+          "CMHandle3": {
+            "cmhandle-properties": {
+              "prop-y": "prop-valuey"
+            }
+          }
+        }
+      ],
+      "datastore": "passthrough-running",
+      "xpath-filter": "//_3gpp-nr-nrm-gnbdufunction:GNBDUFunction/_3gpp-nr-nrm-nrcelldu:NRCellDU/ | //_3gpp-nr-nrm-gnbcuupfunction:GNBCUUPFunction// | //_3gpp-nr-nrm-gnbcucpfunction:GNBCUCPFunction/_3gpp-nr-nrm-nrcelldu:NRCellCU// | //_3gpp-nr-nrm-nrsectorcarrier:NRSectorCarrier//"
+    }
+  }
+}
\ No newline at end of file