Introduce LCM Event V2 17/143317/4
authorToineSiebelink <toine.siebelink@est.tech>
Mon, 9 Feb 2026 10:55:01 +0000 (10:55 +0000)
committerToineSiebelink <toine.siebelink@est.tech>
Mon, 16 Feb 2026 16:51:59 +0000 (16:51 +0000)
- Add config parameter to enable V2 (optional, defaults to V1 if not provided)
- Introduced data holders specific for version 2
- refactor change detector to produce V2 format and then covert to V1
 (easier then the other way around, and better if we in the future remove V1))
- Added ChangeDetector Specs specifically focussed on change in (additional)Properties
- Refactor to create Payload directly instead of using in between classes.
- Renamed ChangeDetector to PayloadFactory

Issue-ID: CPS-2975
Change-Id: I1a5abbbfa9ab71d3826d3d089f8a75001b41bd97
Signed-off-by: ToineSiebelink <toine.siebelink@est.tech>
cps-application/src/main/resources/application.yml
cps-ncmp-events/src/main/resources/schemas/lcm/lcm-event-v2.json
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/CmHandlePropertyChangeDetector.java [deleted file]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/CmHandlePropertyUpdates.java [deleted file]
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventObjectCreator.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventProducer.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/PayloadFactory.java [new file with mode: 0644]
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventProducerSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/PayloadFactorySpec.groovy [moved from cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/CmHandlePropertyChangeDetectorSpec.groovy with 67% similarity]

index f9537b4..bb41e65 100644 (file)
@@ -112,6 +112,7 @@ app:
     lcm:
         events:
             topic: ${LCM_EVENTS_TOPIC:ncmp-events}
+            event-schema-version: "v1"
     dmi:
         cm-events:
             topic: ${DMI_CM_EVENTS_TOPIC:dmi-cm-events}
index 9ebf453..2831e4b 100644 (file)
           "description": "cmHandle id",
           "type": "string"
         },
-        "oldProperties": {
+        "oldValues": {
           "description": "the old cmHandle properties(values) that have changed or been deleted",
           "type": "object",
           "default": null,
           "existingJavaType": "java.util.Map<String,Object>",
           "additionalProperties": false
         },
-        "newProperties": {
+        "newValues": {
           "description": "cmHandle properties(values) that have been changed or been created",
           "type": "object",
           "default": null,
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/CmHandlePropertyChangeDetector.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/CmHandlePropertyChangeDetector.java
deleted file mode 100644 (file)
index cba35e0..0000000
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- * Copyright (C) 2025-2026 OpenInfra Foundation Europe. All rights reserved.
- * ================================================================================
- * 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.impl.inventory.sync.lcm;
-
-import com.google.common.collect.MapDifference;
-import com.google.common.collect.Maps;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import lombok.AccessLevel;
-import lombok.NoArgsConstructor;
-import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle;
-import org.onap.cps.ncmp.events.lcm.Values;
-
-/**
- * Utility class for examining and determining changes in CM handle properties.
- */
-@NoArgsConstructor(access = AccessLevel.PRIVATE)
-public class CmHandlePropertyChangeDetector {
-
-    /**
-     * Determines property (update) values for creating a new CM handle.
-     *
-     * @param ncmpServiceCmHandle the CM handle being created
-     * @return CmHandlePropertyUpdates containing new values for the created CM handle
-     */
-    static CmHandlePropertyUpdates determineUpdatesForCreate(final NcmpServiceCmHandle ncmpServiceCmHandle) {
-        final CmHandlePropertyUpdates cmHandlePropertyUpdates = new CmHandlePropertyUpdates();
-        cmHandlePropertyUpdates.setNewValues(new Values());
-        cmHandlePropertyUpdates.getNewValues().setDataSyncEnabled(getDataSyncEnabledFlag(ncmpServiceCmHandle));
-        cmHandlePropertyUpdates.getNewValues()
-            .setCmHandleState(mapCmHandleStateToLcmEventCmHandleState(ncmpServiceCmHandle));
-        cmHandlePropertyUpdates.getNewValues()
-            .setCmHandleProperties(List.of(ncmpServiceCmHandle.getPublicProperties()));
-        return cmHandlePropertyUpdates;
-    }
-
-    /**
-     * Determines property updates between current and target CM handle states.
-     *
-     * @param currentNcmpServiceCmHandle the current CM handle state
-     * @param targetNcmpServiceCmHandle  the target CM handle state
-     * @return CmHandlePropertyUpdates containing old and new values for changed properties
-     */
-    static CmHandlePropertyUpdates determineUpdates(final NcmpServiceCmHandle currentNcmpServiceCmHandle,
-                                                           final NcmpServiceCmHandle targetNcmpServiceCmHandle) {
-        final boolean hasDataSyncFlagEnabledChanged =
-            hasDataSyncEnabledFlagChanged(currentNcmpServiceCmHandle, targetNcmpServiceCmHandle);
-        final boolean hasCmHandleStateChanged =
-            hasCmHandleStateChanged(currentNcmpServiceCmHandle, targetNcmpServiceCmHandle);
-        final boolean arePublicCmHandlePropertiesEqual =
-            arePublicCmHandlePropertiesEqual(currentNcmpServiceCmHandle.getPublicProperties(),
-                targetNcmpServiceCmHandle.getPublicProperties()
-            );
-
-        final CmHandlePropertyUpdates cmHandlePropertyUpdates = new CmHandlePropertyUpdates();
-
-        if (hasDataSyncFlagEnabledChanged || hasCmHandleStateChanged || (!arePublicCmHandlePropertiesEqual)) {
-            cmHandlePropertyUpdates.setOldValues(new Values());
-            cmHandlePropertyUpdates.setNewValues(new Values());
-        } else {
-            return cmHandlePropertyUpdates;
-        }
-
-        if (hasDataSyncFlagEnabledChanged) {
-            setDataSyncEnabledFlag(currentNcmpServiceCmHandle, targetNcmpServiceCmHandle, cmHandlePropertyUpdates);
-        }
-
-        if (hasCmHandleStateChanged) {
-            setCmHandleStateChange(currentNcmpServiceCmHandle, targetNcmpServiceCmHandle, cmHandlePropertyUpdates);
-        }
-
-        if (!arePublicCmHandlePropertiesEqual) {
-            setPublicCmHandlePropertiesChange(currentNcmpServiceCmHandle, targetNcmpServiceCmHandle,
-                cmHandlePropertyUpdates);
-        }
-
-        return cmHandlePropertyUpdates;
-
-    }
-
-    private static void setDataSyncEnabledFlag(final NcmpServiceCmHandle currentNcmpServiceCmHandle,
-                                               final NcmpServiceCmHandle targetNcmpServiceCmHandle,
-                                               final CmHandlePropertyUpdates cmHandlePropertyUpdates) {
-        cmHandlePropertyUpdates.getOldValues().setDataSyncEnabled(getDataSyncEnabledFlag(currentNcmpServiceCmHandle));
-        cmHandlePropertyUpdates.getNewValues().setDataSyncEnabled(getDataSyncEnabledFlag(targetNcmpServiceCmHandle));
-
-    }
-
-    private static void setCmHandleStateChange(final NcmpServiceCmHandle currentNcmpServiceCmHandle,
-                                               final NcmpServiceCmHandle targetNcmpServiceCmHandle,
-                                               final CmHandlePropertyUpdates cmHandlePropertyUpdates) {
-        cmHandlePropertyUpdates.getOldValues()
-            .setCmHandleState(mapCmHandleStateToLcmEventCmHandleState(currentNcmpServiceCmHandle));
-        cmHandlePropertyUpdates.getNewValues()
-            .setCmHandleState(mapCmHandleStateToLcmEventCmHandleState(targetNcmpServiceCmHandle));
-    }
-
-    private static void setPublicCmHandlePropertiesChange(final NcmpServiceCmHandle currentNcmpServiceCmHandle,
-                                                          final NcmpServiceCmHandle targetNcmpServiceCmHandle,
-                                                          final CmHandlePropertyUpdates cmHandlePropertyUpdates) {
-
-        final Map<String, Map<String, String>> publicCmHandlePropertiesDifference =
-            getPublicCmHandlePropertiesDifference(currentNcmpServiceCmHandle.getPublicProperties(),
-                targetNcmpServiceCmHandle.getPublicProperties()
-            );
-        cmHandlePropertyUpdates.getOldValues()
-            .setCmHandleProperties(List.of(publicCmHandlePropertiesDifference.get("oldValues")));
-        cmHandlePropertyUpdates.getNewValues()
-            .setCmHandleProperties(List.of(publicCmHandlePropertiesDifference.get("newValues")));
-
-    }
-
-    private static Values.CmHandleState mapCmHandleStateToLcmEventCmHandleState(
-        final NcmpServiceCmHandle ncmpServiceCmHandle) {
-        return Values.CmHandleState.fromValue(ncmpServiceCmHandle.getCompositeState().getCmHandleState().name());
-    }
-
-    private static Boolean getDataSyncEnabledFlag(final NcmpServiceCmHandle ncmpServiceCmHandle) {
-        return ncmpServiceCmHandle.getCompositeState().getDataSyncEnabled();
-    }
-
-    private static boolean hasDataSyncEnabledFlagChanged(final NcmpServiceCmHandle currentNcmpServiceCmHandle,
-                                                         final NcmpServiceCmHandle targetNcmpServiceCmHandle) {
-        final Boolean currentDataSyncFlag = currentNcmpServiceCmHandle.getCompositeState().getDataSyncEnabled();
-        final Boolean targetDataSyncFlag = targetNcmpServiceCmHandle.getCompositeState().getDataSyncEnabled();
-
-        if (targetDataSyncFlag == null) {
-            return currentDataSyncFlag != null;
-        }
-
-        return !targetDataSyncFlag.equals(currentDataSyncFlag);
-    }
-
-    private static boolean hasCmHandleStateChanged(final NcmpServiceCmHandle currentNcmpServiceCmHandle,
-                                                   final NcmpServiceCmHandle targetNcmpServiceCmHandle) {
-        return targetNcmpServiceCmHandle.getCompositeState().getCmHandleState()
-            != currentNcmpServiceCmHandle.getCompositeState().getCmHandleState();
-    }
-
-    private static boolean arePublicCmHandlePropertiesEqual(final Map<String, String> currentCmHandleProperties,
-                                                            final Map<String, String> targetCmHandleProperties) {
-        if (targetCmHandleProperties.size() != currentCmHandleProperties.size()) {
-            return false;
-        }
-        return targetCmHandleProperties.equals(currentCmHandleProperties);
-    }
-
-    private static Map<String, Map<String, String>> getPublicCmHandlePropertiesDifference(
-        final Map<String, String> currentCmHandleProperties,
-        final Map<String, String> targetCmHandleProperties) {
-        final Map<String, Map<String, String>> oldAndNewPropertiesDifferenceMap = new HashMap<>(2);
-
-        final MapDifference<String, String> cmHandlePropertiesDifference =
-            Maps.difference(targetCmHandleProperties, currentCmHandleProperties);
-
-        final Map<String, String> oldValues = new HashMap<>(cmHandlePropertiesDifference.entriesOnlyOnRight());
-        final Map<String, String> newValues = new HashMap<>(cmHandlePropertiesDifference.entriesOnlyOnLeft());
-
-        cmHandlePropertiesDifference.entriesDiffering().keySet().forEach(cmHandlePropertyName -> {
-            oldValues.put(cmHandlePropertyName, currentCmHandleProperties.get(cmHandlePropertyName));
-            newValues.put(cmHandlePropertyName, targetCmHandleProperties.get(cmHandlePropertyName));
-        });
-
-        oldAndNewPropertiesDifferenceMap.put("oldValues", oldValues);
-        oldAndNewPropertiesDifferenceMap.put("newValues", newValues);
-
-        return oldAndNewPropertiesDifferenceMap;
-    }
-
-}
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/CmHandlePropertyUpdates.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/CmHandlePropertyUpdates.java
deleted file mode 100644 (file)
index 7a956d1..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * ============LICENSE_START=======================================================
- * Copyright (C) 2025-2026 OpenInfra Foundation Europe. All rights reserved.
- * ================================================================================
- * 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.impl.inventory.sync.lcm;
-
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
-import org.onap.cps.ncmp.events.lcm.Values;
-
-@NoArgsConstructor
-@Getter
-@Setter
-class CmHandlePropertyUpdates {
-    private Values oldValues;
-    private Values newValues;
-}
index 85cc286..28c5dd3 100644 (file)
@@ -31,7 +31,9 @@ import lombok.extern.slf4j.Slf4j;
 import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle;
 import org.onap.cps.ncmp.events.lcm.LcmEventBase;
 import org.onap.cps.ncmp.events.lcm.LcmEventV1;
+import org.onap.cps.ncmp.events.lcm.LcmEventV2;
 import org.onap.cps.ncmp.events.lcm.PayloadV1;
+import org.onap.cps.ncmp.events.lcm.PayloadV2;
 import org.onap.cps.ncmp.impl.utils.EventDateTimeFormatter;
 import org.springframework.stereotype.Service;
 
@@ -58,22 +60,34 @@ public class LcmEventObjectCreator {
             determineEventType(currentNcmpServiceCmHandle, targetNcmpServiceCmHandle);
         final LcmEventV1 lcmEventV1 = new LcmEventV1();
         populateHeaderDetails(lcmEventV1, cmHandleId, lcmEventType);
-        final PayloadV1 payloadV1 = new PayloadV1();
-        payloadV1.setCmHandleId(cmHandleId);
-        payloadV1.setAlternateId(targetNcmpServiceCmHandle.getAlternateId());
-        payloadV1.setModuleSetTag(targetNcmpServiceCmHandle.getModuleSetTag());
-        payloadV1.setDataProducerIdentifier(targetNcmpServiceCmHandle.getDataProducerIdentifier());
-        final CmHandlePropertyUpdates cmHandlePropertyUpdates =
-            determineEventValues(lcmEventType, currentNcmpServiceCmHandle, targetNcmpServiceCmHandle);
-        payloadV1.setOldValues(cmHandlePropertyUpdates.getOldValues());
-        payloadV1.setNewValues(cmHandlePropertyUpdates.getNewValues());
+        final PayloadV1 payloadV1 = PayloadFactory.createPayloadV1(lcmEventType, currentNcmpServiceCmHandle,
+                                                                                 targetNcmpServiceCmHandle);
         lcmEventV1.setEvent(payloadV1);
         return lcmEventV1;
     }
 
+    /**
+     * Create Lifecycle Management Event Version 2.
+     *
+     * @param currentNcmpServiceCmHandle  current ncmp service cmhandle
+     * @param targetNcmpServiceCmHandle   target ncmp service cmhandle
+     * @return Populated LcmEvent Version 2
+     */
+    public LcmEventV2 createLcmEventV2(final NcmpServiceCmHandle currentNcmpServiceCmHandle,
+                                       final NcmpServiceCmHandle targetNcmpServiceCmHandle) {
+        final LcmEventV2 lcmEventV2 = new LcmEventV2();
+        final String cmHandleId = targetNcmpServiceCmHandle.getCmHandleId();
+        final LcmEventType lcmEventType =
+            determineEventType(currentNcmpServiceCmHandle, targetNcmpServiceCmHandle);
+        populateHeaderDetails(lcmEventV2, cmHandleId, lcmEventType);
+        final PayloadV2 payloadV2 = PayloadFactory.createPayloadV2(lcmEventType, currentNcmpServiceCmHandle,
+            targetNcmpServiceCmHandle);
+        lcmEventV2.setEvent(payloadV2);
+        return lcmEventV2;
+    }
+
     private static LcmEventType determineEventType(final NcmpServiceCmHandle currentNcmpServiceCmHandle,
                                                    final NcmpServiceCmHandle targetNcmpServiceCmHandle) {
-
         if (currentNcmpServiceCmHandle.getCompositeState() == null) {
             return CREATE;
         } else if (targetNcmpServiceCmHandle.getCompositeState().getCmHandleState() == DELETED) {
@@ -82,19 +96,6 @@ public class LcmEventObjectCreator {
         return UPDATE;
     }
 
-    private static CmHandlePropertyUpdates determineEventValues(final LcmEventType lcmEventType,
-                                                                final NcmpServiceCmHandle currentNcmpServiceCmHandle,
-                                                                final NcmpServiceCmHandle targetNcmpServiceCmHandle) {
-        if (CREATE == lcmEventType) {
-            return CmHandlePropertyChangeDetector.determineUpdatesForCreate(targetNcmpServiceCmHandle);
-        }
-        if (UPDATE == lcmEventType) {
-            return CmHandlePropertyChangeDetector.determineUpdates(currentNcmpServiceCmHandle,
-                targetNcmpServiceCmHandle);
-        }
-        return new CmHandlePropertyUpdates();
-    }
-
     private void populateHeaderDetails(final LcmEventBase lcmEventBase,
                                        final String eventCorrelationId,
                                        final LcmEventType lcmEventType) {
index c0f7370..0e17a03 100644 (file)
@@ -34,6 +34,9 @@ import org.onap.cps.events.EventProducer;
 import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle;
 import org.onap.cps.ncmp.events.lcm.LcmEventBase;
 import org.onap.cps.ncmp.events.lcm.LcmEventV1;
+import org.onap.cps.ncmp.events.lcm.LcmEventV2;
+import org.onap.cps.ncmp.events.lcm.PayloadV1;
+import org.onap.cps.ncmp.events.lcm.PayloadV2;
 import org.onap.cps.ncmp.events.lcm.Values;
 import org.onap.cps.ncmp.impl.utils.YangDataConverter;
 import org.springframework.beans.factory.annotation.Value;
@@ -61,6 +64,9 @@ public class LcmEventProducer {
     @Value("${app.lcm.events.topic:ncmp-events}")
     private String topicName;
 
+    @Value("${app.lcm.events.event-schema-version:v1}")
+    private String eventSchemaVersion;
+
     @Value("${notification.enabled:true}")
     private boolean notificationsEnabled;
 
@@ -93,14 +99,20 @@ public class LcmEventProducer {
     private void sendLcmEvent(final NcmpServiceCmHandle currentNcmpServiceCmHandle,
                               final NcmpServiceCmHandle targetNcmpServiceCmHandle) {
         if (notificationsEnabled) {
-            final LcmEventV1 lcmEventV1 = lcmEventObjectCreator.createLcmEventV1(currentNcmpServiceCmHandle,
-                                                                               targetNcmpServiceCmHandle);
             final Timer.Sample timerSample = Timer.start(meterRegistry);
+            final LcmEventBase lcmEvent;
+            if (useSchemaV2()) {
+                lcmEvent = lcmEventObjectCreator.createLcmEventV2(currentNcmpServiceCmHandle,
+                                                                  targetNcmpServiceCmHandle);
+            } else {
+                lcmEvent = lcmEventObjectCreator.createLcmEventV1(currentNcmpServiceCmHandle,
+                                                                    targetNcmpServiceCmHandle);
+            }
             try {
-                final Map<String, Object> headersAsMap = extractHeadersAsMap(lcmEventV1);
+                final Map<String, Object> headersAsMap = extractHeadersAsMap(lcmEvent);
                 final String eventKey = currentNcmpServiceCmHandle.getCmHandleId();
-                eventProducer.sendLegacyEvent(topicName, eventKey, headersAsMap, lcmEventV1);
-                recordMetrics(lcmEventV1, timerSample);
+                eventProducer.sendLegacyEvent(topicName, eventKey, headersAsMap, lcmEvent);
+                recordMetrics(lcmEvent, timerSample);
             } catch (final KafkaException e) {
                 log.error("Unable to send message to topic : {} and cause : {}", topicName, e.getMessage());
             }
@@ -121,23 +133,41 @@ public class LcmEventProducer {
         return headersAsMap;
     }
 
-    private void recordMetrics(final LcmEventV1 lcmEventV1, final Timer.Sample timerSample) {
+    private void recordMetrics(final LcmEventBase lcmEvent, final Timer.Sample timerSample) {
         final List<Tag> tags = new ArrayList<>(4);
         tags.add(METRIC_TAG_CLASS);
         tags.add(METRIC_TAG_METHOD);
-        tags.add(createCmHandleStateTag("oldCmHandleState", lcmEventV1.getEvent().getOldValues()));
-        tags.add(createCmHandleStateTag("newCmHandleState", lcmEventV1.getEvent().getNewValues()));
+        if (lcmEvent instanceof LcmEventV2) {
+            final PayloadV2 payloadV2 = ((LcmEventV2) lcmEvent).getEvent();
+            tags.add(createCmHandleStateTagForV2("oldCmHandleState", payloadV2.getOldValues()));
+            tags.add(createCmHandleStateTagForV2("newCmHandleState", payloadV2.getNewValues()));
+        } else {
+            final PayloadV1 payloadV1 = ((LcmEventV1) lcmEvent).getEvent();
+            tags.add(createCmHandleStateTagForV1("oldCmHandleState", payloadV1.getOldValues()));
+            tags.add(createCmHandleStateTagForV1("newCmHandleState", payloadV1.getNewValues()));
+        }
         timerSample.stop(Timer.builder("cps.ncmp.lcm.events.send")
             .description("Time taken to send a LCM event")
             .tags(tags)
             .register(meterRegistry));
     }
 
-    private Tag createCmHandleStateTag(final String tagLabel, final Values values) {
+    private Tag createCmHandleStateTagForV1(final String tagLabel, final Values values) {
         if (values == null) {
             return Tag.of(tagLabel, "N/A");
         }
         return Tag.of(tagLabel, values.getCmHandleState().value());
     }
 
+    private Tag createCmHandleStateTagForV2(final String tagLabel, final Map<String, Object> properties) {
+        if (properties == null || !properties.containsKey("cmHandleState")) {
+            return Tag.of(tagLabel, "N/A");
+        }
+        return Tag.of(tagLabel, properties.get("cmHandleState").toString());
+    }
+
+    private boolean useSchemaV2() {
+        return "v2".equals(eventSchemaVersion);
+    }
+
 }
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/PayloadFactory.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/PayloadFactory.java
new file mode 100644 (file)
index 0000000..3ab8ca1
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2025-2026 OpenInfra Foundation Europe. All rights reserved.
+ * ================================================================================
+ * 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.impl.inventory.sync.lcm;
+
+import com.google.common.collect.MapDifference;
+import com.google.common.collect.Maps;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.onap.cps.ncmp.api.inventory.models.CmHandleState;
+import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle;
+import org.onap.cps.ncmp.events.lcm.PayloadV1;
+import org.onap.cps.ncmp.events.lcm.PayloadV2;
+import org.onap.cps.ncmp.events.lcm.Values;
+
+/**
+ * Utility class for examining and identifying changes in CM handle properties.
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class PayloadFactory {
+
+    static PayloadV1 createPayloadV1(final LcmEventType lcmEventType,
+                                     final NcmpServiceCmHandle currentNcmpServiceCmHandle,
+                                     final NcmpServiceCmHandle targetNcmpServiceCmHandle) {
+        final PayloadV2 payloadV2 = createPayloadV2(lcmEventType, currentNcmpServiceCmHandle,
+                                                                  targetNcmpServiceCmHandle);
+        final PayloadV1 payloadV1 = toV1Format(payloadV2);
+        payloadV1.setAlternateId(targetNcmpServiceCmHandle.getAlternateId());
+        payloadV1.setModuleSetTag(targetNcmpServiceCmHandle.getModuleSetTag());
+        payloadV1.setDataProducerIdentifier(targetNcmpServiceCmHandle.getDataProducerIdentifier());
+        return payloadV1;
+    }
+
+    static PayloadV2 createPayloadV2(final LcmEventType lcmEventType,
+                                     final NcmpServiceCmHandle currentNcmpServiceCmHandle,
+                                     final NcmpServiceCmHandle targetNcmpServiceCmHandle) {
+        final PayloadV2 payloadV2 = switch (lcmEventType) {
+            case CREATE -> getPayLoadForCreate(targetNcmpServiceCmHandle);
+            case UPDATE -> identifyChanges(currentNcmpServiceCmHandle, targetNcmpServiceCmHandle);
+            default -> new PayloadV2();
+        };
+        payloadV2.setCmHandleId(targetNcmpServiceCmHandle.getCmHandleId());
+        return payloadV2;
+    }
+
+    static PayloadV2 getPayLoadForCreate(final NcmpServiceCmHandle ncmpServiceCmHandle) {
+        final PayloadV2 payload = new PayloadV2();
+        final Map<String, Object> newProperties = new HashMap<>();
+        newProperties.put("dataSyncEnabled", ncmpServiceCmHandle.getCompositeState().getDataSyncEnabled());
+        newProperties.put("cmHandleState", ncmpServiceCmHandle.getCompositeState().getCmHandleState().name());
+        newProperties.putAll(ncmpServiceCmHandle.getPublicProperties());
+        payload.setNewValues(newProperties);
+        return payload;
+    }
+
+    static PayloadV2 identifyChanges(final NcmpServiceCmHandle currentNcmpServiceCmHandle,
+                                     final NcmpServiceCmHandle targetNcmpServiceCmHandle) {
+        final Boolean currentDataSync = currentNcmpServiceCmHandle.getCompositeState().getDataSyncEnabled();
+        final Boolean targetDataSync = targetNcmpServiceCmHandle.getCompositeState().getDataSyncEnabled();
+        final boolean dataSyncEnabledChanged = !Objects.equals(currentDataSync, targetDataSync);
+
+        final CmHandleState currentCmHandleState = currentNcmpServiceCmHandle.getCompositeState().getCmHandleState();
+        final CmHandleState targetCmHandleState = targetNcmpServiceCmHandle.getCompositeState().getCmHandleState();
+        final boolean cmHandleStateChanged = !Objects.equals(currentCmHandleState, targetCmHandleState);
+
+        final Map<String, String> currentPublicProperties = currentNcmpServiceCmHandle.getPublicProperties();
+        final Map<String, String> targetPublicProperties = targetNcmpServiceCmHandle.getPublicProperties();
+        final boolean publicPropertiesChanged = !Objects.equals(currentPublicProperties, targetPublicProperties);
+
+        final PayloadV2 payload = new PayloadV2();
+        if (!dataSyncEnabledChanged && !cmHandleStateChanged && !publicPropertiesChanged) {
+            return payload;
+        }
+
+        final Map<String, Object> oldProperties = new HashMap<>();
+        final Map<String, Object> newProperties = new HashMap<>();
+
+        if (dataSyncEnabledChanged) {
+            oldProperties.put("dataSyncEnabled", currentDataSync);
+            newProperties.put("dataSyncEnabled", targetDataSync);
+        }
+
+        if (cmHandleStateChanged) {
+            oldProperties.put("cmHandleState",
+                currentNcmpServiceCmHandle.getCompositeState().getCmHandleState().name());
+            newProperties.put("cmHandleState",
+                targetNcmpServiceCmHandle.getCompositeState().getCmHandleState().name());
+        }
+
+        if (publicPropertiesChanged) {
+            final MapDifference<String, String> mapDifference = Maps.difference(
+                targetNcmpServiceCmHandle.getPublicProperties(),
+                currentNcmpServiceCmHandle.getPublicProperties());
+
+            oldProperties.putAll(mapDifference.entriesOnlyOnRight());
+            newProperties.putAll(mapDifference.entriesOnlyOnLeft());
+
+            mapDifference.entriesDiffering().forEach((key, valueDifference) -> {
+                oldProperties.put(key, valueDifference.rightValue());
+                newProperties.put(key, valueDifference.leftValue());
+            });
+        }
+        payload.setOldValues(oldProperties);
+        payload.setNewValues(newProperties);
+        return payload;
+    }
+
+    private static PayloadV1 toV1Format(final PayloadV2 payloadV2) {
+        final PayloadV1 payloadV1 = new PayloadV1();
+        payloadV1.setCmHandleId(payloadV2.getCmHandleId());
+        if (payloadV2.getOldValues() != null) {
+            payloadV1.setOldValues(mapToValues(payloadV2.getOldValues()));
+        }
+        if (payloadV2.getNewValues() != null) {
+            payloadV1.setNewValues(mapToValues(payloadV2.getNewValues()));
+        }
+        return payloadV1;
+    }
+
+    private static Values mapToValues(final Map<String, Object> properties) {
+        final Values values = new Values();
+        if (properties.containsKey("dataSyncEnabled")) {
+            values.setDataSyncEnabled((Boolean) properties.get("dataSyncEnabled"));
+        }
+        if (properties.containsKey("cmHandleState")) {
+            values.setCmHandleState(Values.CmHandleState.fromValue((String) properties.get("cmHandleState")));
+        }
+        final Map<String, String> publicProperties = new HashMap<>();
+        properties.forEach((key, value) -> {
+            if (!"dataSyncEnabled".equals(key) && !"cmHandleState".equals(key)) {
+                publicProperties.put(key, (String) value);
+            }
+        });
+        if (!publicProperties.isEmpty()) {
+            values.setCmHandleProperties(List.of(publicProperties));
+        }
+        return values;
+    }
+
+}
index 8332258..42bb9d7 100644 (file)
@@ -43,17 +43,20 @@ class LcmEventProducerSpec extends Specification {
         new YangModelCmHandle(id: 'ch-1', compositeState: new CompositeState(cmHandleState: ADVISED), additionalProperties: [], publicProperties: [])
     )
 
-    def 'Create and send lcm event where notifications are #scenario.'() {
+    def 'Create and send lcm event #eventVersion where notifications are #scenario.'() {
         given: 'notificationsEnabled is #notificationsEnabled'
             objectUnderTest.notificationsEnabled = notificationsEnabled
+            objectUnderTest.eventSchemaVersion = eventVersion
         when: 'event send for (batch of) 1 cm handle transition pair (new cm handle going to READY)'
             objectUnderTest.sendLcmEventBatchAsynchronously([cmHandleTransitionPair])
         then: 'producer is called #expectedTimesMethodCalled times with correct identifiers'
             expectedTimesMethodCalled * mockEventProducer.sendLegacyEvent(_, 'ch-1', _, _) >> {
                 args -> {
                     def eventHeaders = args[2]
+                    def event = args[3]
                     assert UUID.fromString(eventHeaders.get('eventId')) != null
                     assert eventHeaders.get('eventCorrelationId') == 'ch-1'
+                    assert event.class.simpleName == expectedEventClass
                 }
             }
         and: 'metrics are recorded with correct tags'
@@ -65,9 +68,10 @@ class LcmEventProducerSpec extends Specification {
                 assert timer == null
             }
         where: 'the following values are used'
-            scenario   | notificationsEnabled || expectedTimesMethodCalled
-            'enabled'  | true                 || 1
-            'disabled' | false                || 0
+            scenario   | eventVersion | notificationsEnabled || expectedTimesMethodCalled | expectedEventClass
+            'enabled'  | 'v1'         | true                 || 1                         | 'LcmEventV1'
+            'enabled'  | 'v2'         | true                 || 1                         | 'LcmEventV2'
+            'disabled' | 'v1'         | false                || 0                         | 'N/A'
     }
 
     def 'Exception while sending message.'(){
@@ -26,17 +26,17 @@ import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle
 import org.onap.cps.ncmp.events.lcm.Values
 import spock.lang.Specification
 
-class CmHandlePropertyChangeDetectorSpec extends Specification {
-
-    def 'Determine updates for create operation.'() {
+class PayloadFactorySpec extends Specification {
+    def 'Create payload for create operation.'() {
         given: 'a new cm handle'
-            def ncmpServiceCmHandle = new NcmpServiceCmHandle(
+            def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: 'ch',
                 compositeState: new CompositeState(dataSyncEnabled: true, cmHandleState: CmHandleState.READY),
                 publicProperties: ['prop1': 'value1']
             )
-        when: 'updates are determined for create'
-            def result = CmHandlePropertyChangeDetector.determineUpdatesForCreate(ncmpServiceCmHandle)
+        when: 'payload is created for create'
+            def result = PayloadFactory.createPayloadV1(LcmEventType.CREATE, null, ncmpServiceCmHandle)
         then: 'new values are populated'
+            assert result.cmHandleId == 'ch'
             assert result.newValues.dataSyncEnabled == true
             assert result.newValues.cmHandleState == Values.CmHandleState.READY
             assert result.newValues.cmHandleProperties == [['prop1': 'value1']]
@@ -44,24 +44,25 @@ class CmHandlePropertyChangeDetectorSpec extends Specification {
             assert result.oldValues == null
     }
 
-    def 'Determine updates when no changes detected.'() {
+    def 'Create payload when no changes detected.'() {
         given: 'current and target cm handles with same properties'
             def currentCmHandle = new NcmpServiceCmHandle(
                 compositeState: new CompositeState(dataSyncEnabled: true, cmHandleState: CmHandleState.READY),
                 publicProperties: ['prop1': 'value1']
             )
-            def targetCmHandle = new NcmpServiceCmHandle(
+            def targetCmHandle = new NcmpServiceCmHandle(cmHandleId: 'ch',
                 compositeState: new CompositeState(dataSyncEnabled: true, cmHandleState: CmHandleState.READY),
                 publicProperties: ['prop1': 'value1']
             )
-        when: 'updates are determined'
-            def result = CmHandlePropertyChangeDetector.determineUpdates(currentCmHandle, targetCmHandle)
+        when: 'payload is created'
+            def result = PayloadFactory.createPayloadV1(LcmEventType.UPDATE, currentCmHandle, targetCmHandle)
         then: 'no updates are detected'
+            assert result.cmHandleId == 'ch'
             assert result.oldValues == null
             assert result.newValues == null
     }
 
-    def 'Determine updates when data sync flag changes.'() {
+    def 'Create payload when data sync flag changes.'() {
         given: 'current and target cm handles with different data sync flags'
             def currentCmHandle = new NcmpServiceCmHandle(
                 compositeState: new CompositeState(dataSyncEnabled: false, cmHandleState: CmHandleState.READY),
@@ -71,14 +72,14 @@ class CmHandlePropertyChangeDetectorSpec extends Specification {
                 compositeState: new CompositeState(dataSyncEnabled: true, cmHandleState: CmHandleState.READY),
                 publicProperties: [:]
             )
-        when: 'updates are determined'
-            def result = CmHandlePropertyChangeDetector.determineUpdates(currentCmHandle, targetCmHandle)
+        when: 'payload is created'
+            def result = PayloadFactory.createPayloadV1(LcmEventType.UPDATE,currentCmHandle, targetCmHandle)
         then: 'data sync flag change is detected'
             assert result.oldValues.dataSyncEnabled == false
             assert result.newValues.dataSyncEnabled == true
     }
 
-    def 'Determine updates when cm handle state changes.'() {
+    def 'Create payload when cm handle state changes.'() {
         given: 'current and target cm handles with different states'
             def currentCmHandle = new NcmpServiceCmHandle(
                 compositeState: new CompositeState(dataSyncEnabled: true, cmHandleState: CmHandleState.ADVISED),
@@ -88,36 +89,36 @@ class CmHandlePropertyChangeDetectorSpec extends Specification {
                 compositeState: new CompositeState(dataSyncEnabled: true, cmHandleState: CmHandleState.READY),
                 publicProperties: [:]
             )
-        when: 'updates are determined'
-            def result = CmHandlePropertyChangeDetector.determineUpdates(currentCmHandle, targetCmHandle)
+        when: 'payload is created'
+            def result = PayloadFactory.createPayloadV1(LcmEventType.UPDATE,currentCmHandle, targetCmHandle)
         then: 'state change is detected'
             assert result.oldValues.cmHandleState == Values.CmHandleState.ADVISED
             assert result.newValues.cmHandleState == Values.CmHandleState.READY
     }
 
-    def 'Determine updates when public properties change.'() {
+    def 'Create payload when public properties change.'() {
         given: 'current and target cm handles with different properties'
             def currentCmHandle = new NcmpServiceCmHandle(
                 compositeState: new CompositeState(dataSyncEnabled: true, cmHandleState: CmHandleState.READY),
-                publicProperties: ['prop1': 'value1', 'prop2': 'value2', 'unchanged': 'sameValue']
+                publicProperties: ['prop1': 'old value', 'prop2': 'to be deleted', 'prop4': 'unchanged']
             )
             def targetCmHandle = new NcmpServiceCmHandle(
                 compositeState: new CompositeState(dataSyncEnabled: true, cmHandleState: CmHandleState.READY),
-                publicProperties: ['prop1': 'newValue1', 'prop3': 'value3', 'unchanged': 'sameValue']
+                publicProperties: ['prop1': 'new value', 'prop3': 'new', 'prop4': 'unchanged']
             )
-        when: 'updates are determined'
-            def result = CmHandlePropertyChangeDetector.determineUpdates(currentCmHandle, targetCmHandle)
+        when: 'payload is created'
+            def result = PayloadFactory.createPayloadV1(LcmEventType.UPDATE,currentCmHandle, targetCmHandle)
         then: 'property changes are detected'
-            assert result.oldValues.cmHandleProperties[0]['prop1'] == 'value1'
-            assert result.oldValues.cmHandleProperties[0]['prop2'] == 'value2'
-            assert result.newValues.cmHandleProperties[0]['prop1'] == 'newValue1'
-            assert result.newValues.cmHandleProperties[0]['prop3'] == 'value3'
+            assert result.oldValues.cmHandleProperties[0]['prop1'] == 'old value'
+            assert result.oldValues.cmHandleProperties[0]['prop2'] == 'to be deleted'
+            assert result.newValues.cmHandleProperties[0]['prop1'] == 'new value'
+            assert result.newValues.cmHandleProperties[0]['prop3'] == 'new'
         and: 'unchanged property is not included in the result'
-            assert !result.oldValues.cmHandleProperties[0].containsKey('unchanged')
-            assert !result.newValues.cmHandleProperties[0].containsKey('unchanged')
+            assert !result.oldValues.cmHandleProperties[0].containsKey('prop4')
+            assert !result.newValues.cmHandleProperties[0].containsKey('prop4')
     }
 
-    def 'Determine updates when multiple changes occur.'() {
+    def 'Create payload when multiple changes occur.'() {
         given: 'current and target cm handles with multiple differences'
             def currentCmHandle = new NcmpServiceCmHandle(
                 compositeState: new CompositeState(dataSyncEnabled: false, cmHandleState: CmHandleState.ADVISED),
@@ -127,8 +128,8 @@ class CmHandlePropertyChangeDetectorSpec extends Specification {
                 compositeState: new CompositeState(dataSyncEnabled: true, cmHandleState: CmHandleState.READY),
                 publicProperties: ['prop1': 'newValue1']
             )
-        when: 'updates are determined'
-            def result = CmHandlePropertyChangeDetector.determineUpdates(currentCmHandle, targetCmHandle)
+        when: 'payload is created'
+            def result = PayloadFactory.createPayloadV1(LcmEventType.UPDATE,currentCmHandle, targetCmHandle)
         then: 'all changes are detected'
             assert result.oldValues.dataSyncEnabled == false
             assert result.newValues.dataSyncEnabled == true
@@ -137,4 +138,23 @@ class CmHandlePropertyChangeDetectorSpec extends Specification {
             assert result.oldValues.cmHandleProperties[0]['prop1'] == 'value1'
             assert result.newValues.cmHandleProperties[0]['prop1'] == 'newValue1'
     }
+
+    def 'Create payload for delete operation.'() {
+        given: 'a cm handle being deleted'
+            def currentCmHandle = new NcmpServiceCmHandle(
+                compositeState: new CompositeState(dataSyncEnabled: true, cmHandleState: CmHandleState.READY),
+                publicProperties: ['prop1': 'value1']
+            )
+            def targetCmHandle = new NcmpServiceCmHandle(cmHandleId: 'ch',
+                compositeState: new CompositeState(dataSyncEnabled: false, cmHandleState: CmHandleState.DELETED),
+                publicProperties: ['prop1': 'value1']
+            )
+        when: 'payload is created for delete'
+            def result = PayloadFactory.createPayloadV1(LcmEventType.DELETE, currentCmHandle, targetCmHandle)
+        then: 'cmHandleId is populated'
+            assert result.cmHandleId == 'ch'
+        and: 'no values are populated'
+            assert result.oldValues == null
+            assert result.newValues == null
+    }
 }