From 976fa39e54ca4f892d9510723b6c397c5b2a8e2e Mon Sep 17 00:00:00 2001 From: Arpit Singh Date: Thu, 26 Jun 2025 20:17:11 +0530 Subject: [PATCH] Part 1: Refactor CPS Delta code to utility class - Refactoring Delta code to separate utility class for a more granular and manageble code. - Patch 1 refactors code for 'update' action as the code is shared between old and new delta report formats. Issue-ID: CPS-2838 Change-Id: I74425817f1b21a1b369986f65f12dba8a501f3ea Signed-off-by: Arpit Singh --- .../org/onap/cps/impl/CpsDeltaServiceImpl.java | 147 ++--------------- .../cps/utils/deltareport/DeltaReportHelper.java | 178 +++++++++++++++++++++ .../onap/cps/impl/CpsDeltaServiceImplSpec.groovy | 4 +- 3 files changed, 192 insertions(+), 137 deletions(-) create mode 100644 cps-service/src/main/java/org/onap/cps/utils/deltareport/DeltaReportHelper.java diff --git a/cps-service/src/main/java/org/onap/cps/impl/CpsDeltaServiceImpl.java b/cps-service/src/main/java/org/onap/cps/impl/CpsDeltaServiceImpl.java index c902c00848..1a5cdab290 100644 --- a/cps-service/src/main/java/org/onap/cps/impl/CpsDeltaServiceImpl.java +++ b/cps-service/src/main/java/org/onap/cps/impl/CpsDeltaServiceImpl.java @@ -27,11 +27,9 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -43,11 +41,11 @@ import org.onap.cps.api.model.Anchor; import org.onap.cps.api.model.DataNode; import org.onap.cps.api.model.DeltaReport; import org.onap.cps.api.parameters.FetchDescendantsOption; -import org.onap.cps.cpspath.parser.CpsPathQuery; import org.onap.cps.cpspath.parser.CpsPathUtil; import org.onap.cps.utils.DataMapUtils; import org.onap.cps.utils.DataMapper; import org.onap.cps.utils.JsonObjectMapper; +import org.onap.cps.utils.deltareport.DeltaReportHelper; import org.springframework.stereotype.Service; @Slf4j @@ -60,6 +58,7 @@ public class CpsDeltaServiceImpl implements CpsDeltaService { private final DataNodeFactory dataNodeFactory; private final DataMapper dataMapper; private final JsonObjectMapper jsonObjectMapper; + private final DeltaReportHelper deltaReportHelper; @Override @Timed(value = "cps.delta.service.get.delta", @@ -99,7 +98,7 @@ public class CpsDeltaServiceImpl implements CpsDeltaService { return getDeltaReports(sourceDataNodesRebuilt, targetDataNodes, groupDataNodes); } - private static List getDeltaReports(final Collection sourceDataNodes, + private List getDeltaReports(final Collection sourceDataNodes, final Collection targetDataNodes, final boolean groupDataNodes) { @@ -127,7 +126,7 @@ public class CpsDeltaServiceImpl implements CpsDeltaService { return xpathToDataNode; } - private static List getRemovedAndUpdatedDeltaReports( + private List getRemovedAndUpdatedDeltaReports( final Map xpathToSourceDataNodes, final Map xpathToTargetDataNodes) { final List removedAndUpdatedDeltaReportEntries = new ArrayList<>(); @@ -139,7 +138,7 @@ public class CpsDeltaServiceImpl implements CpsDeltaService { if (targetDataNode == null) { deltaReports = getDeltaReportsForRemove(xpath, sourceDataNode); } else { - deltaReports = getDeltaReportsForUpdates(xpath, sourceDataNode, targetDataNode); + deltaReports = deltaReportHelper.createDeltaReportsForUpdates(xpath, sourceDataNode, targetDataNode); } removedAndUpdatedDeltaReportEntries.addAll(deltaReports); } @@ -155,132 +154,6 @@ public class CpsDeltaServiceImpl implements CpsDeltaService { return deltaReportEntriesForRemove; } - private static List getDeltaReportsForUpdates(final String xpath, final DataNode sourceDataNode, - final DataNode targetDataNode) { - final List deltaReportEntriesForUpdates = new ArrayList<>(); - final Map, Map> updatedSourceDataToTargetData = - getUpdatedSourceAndTargetDataNode(sourceDataNode, targetDataNode); - if (!updatedSourceDataToTargetData.isEmpty()) { - addUpdatedDataToDeltaReport(xpath, updatedSourceDataToTargetData, deltaReportEntriesForUpdates); - } - return deltaReportEntriesForUpdates; - } - - private static Map, Map> getUpdatedSourceAndTargetDataNode( - final DataNode sourceDataNode, - final DataNode targetDataNode) { - final Map updatedLeavesInSourceData = new HashMap<>(); - final Map updatedLeavesInTargetData = new HashMap<>(); - processSourceAndTargetDataNode(sourceDataNode, targetDataNode, - updatedLeavesInSourceData, updatedLeavesInTargetData); - processUniqueDataInTargetDataNode(sourceDataNode, targetDataNode, - updatedLeavesInSourceData, updatedLeavesInTargetData); - final Map updatedSourceData = - getUpdatedNodeData(sourceDataNode, updatedLeavesInSourceData); - final Map updatedTargetData = - getUpdatedNodeData(targetDataNode, updatedLeavesInTargetData); - if (updatedSourceData.isEmpty() && updatedTargetData.isEmpty()) { - return Collections.emptyMap(); - } - return Collections.singletonMap(updatedSourceData, updatedTargetData); - } - - private static void processSourceAndTargetDataNode( - final DataNode sourceDataNode, - final DataNode targetDataNode, - final Map sourceDataInDeltaReport, - final Map targetDataInDeltaReport) { - final Map leavesOfSourceDataNode = sourceDataNode.getLeaves(); - final Map leavesOfTargetDataNode = targetDataNode.getLeaves(); - for (final Map.Entry entry: leavesOfSourceDataNode.entrySet()) { - final String key = entry.getKey(); - final Serializable sourceLeaf = entry.getValue(); - final Serializable targetLeaf = leavesOfTargetDataNode.get(key); - compareLeaves(key, sourceLeaf, targetLeaf, sourceDataInDeltaReport, targetDataInDeltaReport); - } - } - - private static void processUniqueDataInTargetDataNode( - final DataNode sourceDataNode, - final DataNode targetDataNode, - final Map sourceDataInDeltaReport, - final Map targetDataInDeltaReport) { - final Map leavesOfSourceDataNode = sourceDataNode.getLeaves(); - final Map uniqueLeavesOfTargetDataNode = - new HashMap<>(targetDataNode.getLeaves()); - uniqueLeavesOfTargetDataNode.keySet().removeAll(leavesOfSourceDataNode.keySet()); - for (final Map.Entry entry: uniqueLeavesOfTargetDataNode.entrySet()) { - final String key = entry.getKey(); - final Serializable targetLeaf = entry.getValue(); - final Serializable sourceLeaf = leavesOfSourceDataNode.get(key); - compareLeaves(key, sourceLeaf, targetLeaf, sourceDataInDeltaReport, targetDataInDeltaReport); - } - } - - private static void compareLeaves(final String key, - final Serializable sourceLeaf, - final Serializable targetLeaf, - final Map sourceDataInDeltaReport, - final Map targetDataInDeltaReport) { - if (sourceLeaf != null && targetLeaf != null) { - if (!Objects.equals(sourceLeaf, targetLeaf)) { - sourceDataInDeltaReport.put(key, sourceLeaf); - targetDataInDeltaReport.put(key, targetLeaf); - } - } else if (sourceLeaf == null) { - targetDataInDeltaReport.put(key, targetLeaf); - } else { - sourceDataInDeltaReport.put(key, sourceLeaf); - } - } - - private static Map getUpdatedNodeData(final DataNode dataNode, - final Map updatedLeaves) { - final Map updatedSourceData = new HashMap<>(); - if (!updatedLeaves.isEmpty()) { - final String xpath = dataNode.getXpath(); - if (CpsPathUtil.isPathToListElement(xpath)) { - addKeyLeavesToUpdatedData(xpath, updatedLeaves); - } - final Collection updatedDataNode = buildUpdatedDataNode(dataNode, updatedLeaves); - updatedSourceData.putAll(getCondensedDataForDeltaReport(updatedDataNode)); - } - return updatedSourceData; - } - - private static void addKeyLeavesToUpdatedData(final String xpath, - final Map updatedLeaves) { - final Map keyLeaves = new HashMap<>(); - final List leafConditions = CpsPathUtil.getCpsPathQuery(xpath).getLeafConditions(); - for (final CpsPathQuery.LeafCondition leafCondition: leafConditions) { - final String leafName = leafCondition.name(); - final Serializable leafValue = (Serializable) leafCondition.value(); - keyLeaves.put(leafName, leafValue); - } - updatedLeaves.putAll(keyLeaves); - } - - private static Collection buildUpdatedDataNode(final DataNode dataNode, - final Map updatedLeaves) { - final DataNode updatedDataNode = new DataNodeBuilder() - .withXpath(dataNode.getXpath()) - .withModuleNamePrefix(dataNode.getModuleNamePrefix()) - .withLeaves(updatedLeaves) - .build(); - return Collections.singletonList(updatedDataNode); - } - - private static void addUpdatedDataToDeltaReport(final String xpath, - final Map, Map> updatedSourceDataToTargetData, - final List deltaReportEntriesForUpdates) { - for (final Map.Entry, Map> entry: - updatedSourceDataToTargetData.entrySet()) { - final DeltaReport updatedDataForDeltaReport = new DeltaReportBuilder().actionReplace().withXpath(xpath) - .withSourceData(entry.getKey()).withTargetData(entry.getValue()).build(); - deltaReportEntriesForUpdates.add(updatedDataForDeltaReport); - } - } - private static List getAddedDeltaReports(final Map xpathToSourceDataNodes, final Map xpathToTargetDataNodes) { @@ -323,7 +196,7 @@ public class CpsDeltaServiceImpl implements CpsDeltaService { } } - private static List getCondensedDeltaReports(final Collection sourceDataNodes, + private List getCondensedDeltaReports(final Collection sourceDataNodes, final Collection targetDataNodes) { final List deltaReportEntries = new ArrayList<>(); @@ -348,21 +221,23 @@ public class CpsDeltaServiceImpl implements CpsDeltaService { return deltaReportEntriesForRemove; } - private static List getCondensedUpdatedDeltaReports(final Collection sourceDataNodes, + private List getCondensedUpdatedDeltaReports(final Collection sourceDataNodes, final Map xpathToTargetDataNodes) { final List deltaReportEntriesForUpdates = new ArrayList<>(); for (final DataNode sourceDataNode : sourceDataNodes) { final String xpath = sourceDataNode.getXpath(); if (xpathToTargetDataNodes.containsKey(xpath)) { final DataNode targetDataNode = xpathToTargetDataNodes.get(xpath); - deltaReportEntriesForUpdates.addAll(getDeltaReportsForUpdates(xpath, sourceDataNode, targetDataNode)); + final List updatedDataForDeltaReport = + deltaReportHelper.createDeltaReportsForUpdates(xpath, sourceDataNode, targetDataNode); + deltaReportEntriesForUpdates.addAll(updatedDataForDeltaReport); getCondensedDeltaReportsForChildDataNodes(sourceDataNode, targetDataNode, deltaReportEntriesForUpdates); } } return deltaReportEntriesForUpdates; } - private static void getCondensedDeltaReportsForChildDataNodes(final DataNode sourceDataNode, + private void getCondensedDeltaReportsForChildDataNodes(final DataNode sourceDataNode, final DataNode targetDataNode, final List deltaReportEntries) { final Collection childrenOfSourceDataNodes = sourceDataNode.getChildDataNodes(); diff --git a/cps-service/src/main/java/org/onap/cps/utils/deltareport/DeltaReportHelper.java b/cps-service/src/main/java/org/onap/cps/utils/deltareport/DeltaReportHelper.java new file mode 100644 index 0000000000..01127183aa --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/utils/deltareport/DeltaReportHelper.java @@ -0,0 +1,178 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2025 TechMahindra Ltd. + * ================================================================================ + * 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.utils.deltareport; + +import java.io.Serializable; +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.Objects; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.api.model.DataNode; +import org.onap.cps.api.model.DeltaReport; +import org.onap.cps.cpspath.parser.CpsPathQuery; +import org.onap.cps.cpspath.parser.CpsPathUtil; +import org.onap.cps.impl.DataNodeBuilder; +import org.onap.cps.impl.DeltaReportBuilder; +import org.onap.cps.utils.DataMapUtils; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +public class DeltaReportHelper { + + /** + * Get delta report entries for updates. + * + * @param xpath the xpath of the data node + * @param sourceDataNode the source data node + * @param targetDataNode the target data node + * @return a list of delta report entries for updates + */ + public List createDeltaReportsForUpdates(final String xpath, final DataNode sourceDataNode, + final DataNode targetDataNode) { + final List deltaReportEntriesForUpdates = new ArrayList<>(); + final Map, Map> updatedSourceDataToTargetData = + getUpdatedSourceAndTargetDataNode(sourceDataNode, targetDataNode); + if (!updatedSourceDataToTargetData.isEmpty()) { + addUpdatedDataToDeltaReport(xpath, updatedSourceDataToTargetData, deltaReportEntriesForUpdates); + } + return deltaReportEntriesForUpdates; + } + + private static Map, Map> getUpdatedSourceAndTargetDataNode( + final DataNode sourceDataNode, + final DataNode targetDataNode) { + final Map updatedLeavesInSourceData = new HashMap<>(); + final Map updatedLeavesInTargetData = new HashMap<>(); + processSourceAndTargetDataNode(sourceDataNode, targetDataNode, + updatedLeavesInSourceData, updatedLeavesInTargetData); + processUniqueDataInTargetDataNode(sourceDataNode, targetDataNode, updatedLeavesInTargetData); + final Map updatedSourceData = + getUpdatedNodeData(sourceDataNode, updatedLeavesInSourceData); + final Map updatedTargetData = + getUpdatedNodeData(targetDataNode, updatedLeavesInTargetData); + if (updatedSourceData.isEmpty() && updatedTargetData.isEmpty()) { + return Collections.emptyMap(); + } + return Collections.singletonMap(updatedSourceData, updatedTargetData); + } + + private static void addUpdatedDataToDeltaReport(final String xpath, + final Map, + Map> updatedSourceDataToTargetData, + final List deltaReportEntriesForUpdates) { + for (final Map.Entry, Map> entry: + updatedSourceDataToTargetData.entrySet()) { + final DeltaReport updatedDataForDeltaReport = new DeltaReportBuilder().actionReplace().withXpath(xpath) + .withSourceData(entry.getKey()).withTargetData(entry.getValue()).build(); + deltaReportEntriesForUpdates.add(updatedDataForDeltaReport); + } + } + + private static void processSourceAndTargetDataNode( + final DataNode sourceDataNode, + final DataNode targetDataNode, + final Map sourceDataInDeltaReport, + final Map targetDataInDeltaReport) { + final Map leavesOfSourceDataNode = sourceDataNode.getLeaves(); + final Map leavesOfTargetDataNode = targetDataNode.getLeaves(); + for (final Map.Entry entry: leavesOfSourceDataNode.entrySet()) { + final String key = entry.getKey(); + final Serializable sourceLeaf = entry.getValue(); + final Serializable targetLeaf = leavesOfTargetDataNode.get(key); + compareLeaves(key, sourceLeaf, targetLeaf, sourceDataInDeltaReport, targetDataInDeltaReport); + } + } + + private static void processUniqueDataInTargetDataNode( + final DataNode sourceDataNode, + final DataNode targetDataNode, + final Map targetDataInDeltaReport) { + final Map leavesOfSourceDataNode = sourceDataNode.getLeaves(); + final Map uniqueLeavesOfTargetDataNode = + new HashMap<>(targetDataNode.getLeaves()); + uniqueLeavesOfTargetDataNode.keySet().removeAll(leavesOfSourceDataNode.keySet()); + targetDataInDeltaReport.putAll(uniqueLeavesOfTargetDataNode); + } + + private static void compareLeaves(final String key, + final Serializable sourceLeaf, + final Serializable targetLeaf, + final Map sourceDataInDeltaReport, + final Map targetDataInDeltaReport) { + if (targetLeaf != null) { + if (!Objects.equals(sourceLeaf, targetLeaf)) { + sourceDataInDeltaReport.put(key, sourceLeaf); + targetDataInDeltaReport.put(key, targetLeaf); + } + } else { + sourceDataInDeltaReport.put(key, sourceLeaf); + } + } + + private static Map getUpdatedNodeData(final DataNode dataNode, + final Map updatedLeaves) { + final Map updatedSourceData = new HashMap<>(); + if (!updatedLeaves.isEmpty()) { + final String xpath = dataNode.getXpath(); + if (CpsPathUtil.isPathToListElement(xpath)) { + addKeyLeavesToUpdatedData(xpath, updatedLeaves); + } + final Collection updatedDataNode = buildUpdatedDataNode(dataNode, updatedLeaves); + updatedSourceData.putAll(getCondensedDataForDeltaReport(updatedDataNode)); + } + return updatedSourceData; + } + + private static void addKeyLeavesToUpdatedData(final String xpath, + final Map updatedLeaves) { + final Map keyLeaves = new HashMap<>(); + final List leafConditions = CpsPathUtil.getCpsPathQuery(xpath).getLeafConditions(); + for (final CpsPathQuery.LeafCondition leafCondition: leafConditions) { + final String leafName = leafCondition.name(); + final Serializable leafValue = (Serializable) leafCondition.value(); + keyLeaves.put(leafName, leafValue); + } + updatedLeaves.putAll(keyLeaves); + } + + private static Collection buildUpdatedDataNode(final DataNode dataNode, + final Map updatedLeaves) { + final DataNode updatedDataNode = new DataNodeBuilder() + .withXpath(dataNode.getXpath()) + .withModuleNamePrefix(dataNode.getModuleNamePrefix()) + .withLeaves(updatedLeaves) + .build(); + return Collections.singletonList(updatedDataNode); + } + + private static Map getCondensedDataForDeltaReport(final Collection dataNodes) { + final DataNode containerNode = new DataNodeBuilder().withChildDataNodes(dataNodes).build(); + final Map condensedData = DataMapUtils.toDataMap(containerNode); + return condensedData.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, + entry -> (Serializable) entry.getValue())); + } +} diff --git a/cps-service/src/test/groovy/org/onap/cps/impl/CpsDeltaServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/impl/CpsDeltaServiceImplSpec.groovy index 82fc893abb..97618defa8 100644 --- a/cps-service/src/test/groovy/org/onap/cps/impl/CpsDeltaServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/impl/CpsDeltaServiceImplSpec.groovy @@ -36,6 +36,7 @@ import org.onap.cps.utils.JsonObjectMapper import org.onap.cps.utils.PrefixResolver import org.onap.cps.utils.YangParser import org.onap.cps.utils.YangParserHelper +import org.onap.cps.utils.deltareport.DeltaReportHelper import org.onap.cps.yang.TimedYangTextSchemaSourceSetBuilder import org.onap.cps.yang.YangTextSchemaSourceSet import org.onap.cps.yang.YangTextSchemaSourceSetBuilder @@ -58,7 +59,8 @@ class CpsDeltaServiceImplSpec extends Specification { def mockPrefixResolver = Mock(PrefixResolver) def dataMapper = new DataMapper(mockCpsAnchorService, mockPrefixResolver) def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()) - def objectUnderTest = new CpsDeltaServiceImpl(mockCpsAnchorService, mockCpsDataService, dataNodeFactory, dataMapper, jsonObjectMapper) + def deltaReportHelper = new DeltaReportHelper() + def objectUnderTest = new CpsDeltaServiceImpl(mockCpsAnchorService, mockCpsDataService, dataNodeFactory, dataMapper, jsonObjectMapper, deltaReportHelper) static def bookstoreDataNodeWithParentXpath = [new DataNode(xpath: '/bookstore', leaves: ['bookstore-name': 'Easons'])] static def bookstoreDataNodeWithChildXpath = [new DataNode(xpath: '/bookstore/categories[@code=\'02\']', leaves: ['code': '02', 'name': 'Kids'])] -- 2.16.6