diff options
author | Toine Siebelink <toine.siebelink@est.tech> | 2024-01-18 16:51:48 +0000 |
---|---|---|
committer | Gerrit Code Review <gerrit@onap.org> | 2024-01-18 16:51:48 +0000 |
commit | 83433140bac9358aef50036081d1f7079ac10c01 (patch) | |
tree | 39cd88b32ee3f3b45f02f1b1e5ce28ceeb548365 /cps-service/src | |
parent | 5ee1836bd9a2336afad291623db5b265f27d801a (diff) | |
parent | 885980d8ebf6bc8be9efad7ce9094deeb602a076 (diff) |
Merge "CPS Delta API: Update action for delta service"
Diffstat (limited to 'cps-service/src')
4 files changed, 185 insertions, 42 deletions
diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDeltaServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDeltaServiceImpl.java index 683ddce3d1..1e1fe819ac 100644 --- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDeltaServiceImpl.java +++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDeltaServiceImpl.java @@ -28,7 +28,7 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import lombok.NoArgsConstructor; +import java.util.Objects; import lombok.extern.slf4j.Slf4j; import org.onap.cps.api.CpsDeltaService; import org.onap.cps.spi.model.DataNode; @@ -38,7 +38,6 @@ import org.springframework.stereotype.Service; @Slf4j @Service -@NoArgsConstructor public class CpsDeltaServiceImpl implements CpsDeltaService { @Override @@ -50,7 +49,7 @@ public class CpsDeltaServiceImpl implements CpsDeltaService { final Map<String, DataNode> xpathToSourceDataNodes = convertToXPathToDataNodesMap(sourceDataNodes); final Map<String, DataNode> xpathToTargetDataNodes = convertToXPathToDataNodesMap(targetDataNodes); - deltaReport.addAll(getRemovedDeltaReports(xpathToSourceDataNodes, xpathToTargetDataNodes)); + deltaReport.addAll(getRemovedAndUpdatedDeltaReports(xpathToSourceDataNodes, xpathToTargetDataNodes)); deltaReport.addAll(getAddedDeltaReports(xpathToSourceDataNodes, xpathToTargetDataNodes)); @@ -70,26 +69,122 @@ public class CpsDeltaServiceImpl implements CpsDeltaService { return xpathToDataNode; } - private static List<DeltaReport> getRemovedDeltaReports( - final Map<String, DataNode> xpathToSourceDataNodes, - final Map<String, DataNode> xpathToTargetDataNodes) { - - final List<DeltaReport> removedDeltaReportEntries = new ArrayList<>(); + private static List<DeltaReport> getRemovedAndUpdatedDeltaReports( + final Map<String, DataNode> xpathToSourceDataNodes, + final Map<String, DataNode> xpathToTargetDataNodes) { + final List<DeltaReport> removedAndUpdatedDeltaReportEntries = new ArrayList<>(); for (final Map.Entry<String, DataNode> entry: xpathToSourceDataNodes.entrySet()) { final String xpath = entry.getKey(); final DataNode sourceDataNode = entry.getValue(); final DataNode targetDataNode = xpathToTargetDataNodes.get(xpath); - + final List<DeltaReport> deltaReports; if (targetDataNode == null) { - final Map<String, Serializable> sourceDataNodeLeaves = sourceDataNode.getLeaves(); - final DeltaReport removedData = new DeltaReportBuilder().actionRemove().withXpath(xpath) - .withSourceData(sourceDataNodeLeaves).build(); - removedDeltaReportEntries.add(removedData); + deltaReports = getRemovedDeltaReports(xpath, sourceDataNode); + } else { + deltaReports = getUpdatedDeltaReports(xpath, sourceDataNode, targetDataNode); } + removedAndUpdatedDeltaReportEntries.addAll(deltaReports); } + return removedAndUpdatedDeltaReportEntries; + } + + private static List<DeltaReport> getRemovedDeltaReports(final String xpath, final DataNode sourceDataNode) { + final List<DeltaReport> removedDeltaReportEntries = new ArrayList<>(); + final Map<String, Serializable> sourceDataNodeLeaves = sourceDataNode.getLeaves(); + final DeltaReport removedDeltaReportEntry = new DeltaReportBuilder().actionRemove().withXpath(xpath) + .withSourceData(sourceDataNodeLeaves).build(); + removedDeltaReportEntries.add(removedDeltaReportEntry); return removedDeltaReportEntries; } + private static List<DeltaReport> getUpdatedDeltaReports(final String xpath, final DataNode sourceDataNode, + final DataNode targetDataNode) { + final List<DeltaReport> updatedDeltaReportEntries = new ArrayList<>(); + final Map<Map<String, Serializable>, Map<String, Serializable>> updatedLeavesAsSourceDataToTargetData = + getUpdatedLeavesBetweenSourceAndTargetDataNode(sourceDataNode.getLeaves(), targetDataNode.getLeaves()); + addUpdatedLeavesToDeltaReport(xpath, updatedLeavesAsSourceDataToTargetData, updatedDeltaReportEntries); + return updatedDeltaReportEntries; + } + + private static Map<Map<String, Serializable>, + Map<String, Serializable>> getUpdatedLeavesBetweenSourceAndTargetDataNode( + final Map<String, Serializable> leavesOfSourceDataNode, + final Map<String, Serializable> leavesOfTargetDataNode) { + final Map<Map<String, Serializable>, Map<String, Serializable>> updatedLeavesAsSourceDataToTargetData = + new LinkedHashMap<>(); + final Map<String, Serializable> sourceDataInDeltaReport = new LinkedHashMap<>(); + final Map<String, Serializable> targetDataInDeltaReport = new LinkedHashMap<>(); + processLeavesPresentInSourceAndTargetDataNode(leavesOfSourceDataNode, leavesOfTargetDataNode, + sourceDataInDeltaReport, targetDataInDeltaReport); + processLeavesUniqueInTargetDataNode(leavesOfSourceDataNode, leavesOfTargetDataNode, + sourceDataInDeltaReport, targetDataInDeltaReport); + final boolean isUpdatedDataInDeltaReport = + !sourceDataInDeltaReport.isEmpty() || !targetDataInDeltaReport.isEmpty(); + if (isUpdatedDataInDeltaReport) { + updatedLeavesAsSourceDataToTargetData.put(sourceDataInDeltaReport, targetDataInDeltaReport); + } + return updatedLeavesAsSourceDataToTargetData; + } + + private static void processLeavesPresentInSourceAndTargetDataNode( + final Map<String, Serializable> leavesOfSourceDataNode, + final Map<String, Serializable> leavesOfTargetDataNode, + final Map<String, Serializable> sourceDataInDeltaReport, + final Map<String, Serializable> targetDataInDeltaReport) { + for (final Map.Entry<String, Serializable> 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 processLeavesUniqueInTargetDataNode( + final Map<String, Serializable> leavesOfSourceDataNode, + final Map<String, Serializable> leavesOfTargetDataNode, + final Map<String, Serializable> sourceDataInDeltaReport, + final Map<String, Serializable> targetDataInDeltaReport) { + final Map<String, Serializable> uniqueLeavesOfTargetDataNode = + new LinkedHashMap<>(leavesOfTargetDataNode); + uniqueLeavesOfTargetDataNode.keySet().removeAll(leavesOfSourceDataNode.keySet()); + for (final Map.Entry<String, Serializable> 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<String, Serializable> sourceDataInDeltaReport, + final Map<String, Serializable> targetDataInDeltaReport) { + if (sourceLeaf != null && targetLeaf != null) { + if (!Objects.equals(sourceLeaf, targetLeaf)) { + sourceDataInDeltaReport.put(key, sourceLeaf); + targetDataInDeltaReport.put(key, targetLeaf); + } + } else if (sourceLeaf != null) { + sourceDataInDeltaReport.put(key, sourceLeaf); + } else if (targetLeaf != null) { + targetDataInDeltaReport.put(key, targetLeaf); + } + } + + private static void addUpdatedLeavesToDeltaReport(final String xpath, + final Map<Map<String, Serializable>, Map<String, + Serializable>> updatedLeavesAsSourceDataToTargetData, + final List<DeltaReport> updatedDeltaReportEntries) { + for (final Map.Entry<Map<String, Serializable>, Map<String, Serializable>> entry: + updatedLeavesAsSourceDataToTargetData.entrySet()) { + final DeltaReport updatedDataForDeltaReport = new DeltaReportBuilder().actionUpdate() + .withXpath(xpath).withSourceData(entry.getKey()).withTargetData(entry.getValue()).build(); + updatedDeltaReportEntries.add(updatedDataForDeltaReport); + } + + } + private static List<DeltaReport> getAddedDeltaReports(final Map<String, DataNode> xpathToSourceDataNodes, final Map<String, DataNode> xpathToTargetDataNodes) { diff --git a/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReport.java b/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReport.java index b9c05dcf02..fb9c1971b2 100644 --- a/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReport.java +++ b/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReport.java @@ -32,6 +32,7 @@ public class DeltaReport { public static final String ADD_ACTION = "add"; public static final String REMOVE_ACTION = "remove"; + public static final String UPDATE_ACTION = "update"; DeltaReport() {} diff --git a/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReportBuilder.java b/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReportBuilder.java index cef6ca3fa2..1e151eeb2d 100644 --- a/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReportBuilder.java +++ b/cps-service/src/main/java/org/onap/cps/spi/model/DeltaReportBuilder.java @@ -58,6 +58,11 @@ public class DeltaReportBuilder { return this; } + public DeltaReportBuilder actionUpdate() { + this.action = DeltaReport.UPDATE_ACTION; + return this; + } + /** * To create a single entry of {@link DeltaReport}. * diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDeltaServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDeltaServiceImplSpec.groovy index a4f4339737..e21c6f0e2f 100644 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDeltaServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDeltaServiceImplSpec.groovy @@ -21,7 +21,6 @@ package org.onap.cps.api.impl import org.onap.cps.spi.model.DataNode -import org.onap.cps.spi.model.DataNodeBuilder import spock.lang.Shared import spock.lang.Specification @@ -29,38 +28,81 @@ class CpsDeltaServiceImplSpec extends Specification{ def objectUnderTest = new CpsDeltaServiceImpl() - @Shared - def dataNodeWithLeafAndChildDataNode = [new DataNodeBuilder().withXpath('/parent').withLeaves(['parent-leaf': 'parent-payload']) - .withChildDataNodes([new DataNodeBuilder().withXpath("/parent/child").withLeaves('child-leaf': 'child-payload').build()]).build()] - @Shared - def dataNodeWithChildDataNode = [new DataNodeBuilder().withXpath('/parent').withLeaves(['parent-leaf': 'parent-payload']) - .withChildDataNodes([new DataNodeBuilder().withXpath("/parent/child").build()]).build()] - @Shared - def emptyDataNode = [new DataNodeBuilder().withXpath('/parent').build()] - def 'Get delta between data nodes for removed data where source data node has #scenario'() { + static def sourceDataNodeWithLeafData = [new DataNode(xpath: '/parent', leaves: ['parent-leaf': 'parent-payload-in-source'])] + static def sourceDataNodeWithoutLeafData = [new DataNode(xpath: '/parent')] + static def targetDataNodeWithLeafData = [new DataNode(xpath: '/parent', leaves: ['parent-leaf': 'parent-payload-in-target'])] + static def targetDataNodeWithoutLeafData = [new DataNode(xpath: '/parent')] + static def sourceDataNodeWithMultipleLeaves = [new DataNode(xpath: '/parent', leaves: ['leaf-1': 'leaf-1-in-source', 'leaf-2': 'leaf-2-in-source'])] + static def targetDataNodeWithMultipleLeaves = [new DataNode(xpath: '/parent', leaves: ['leaf-1': 'leaf-1-in-target', 'leaf-2': 'leaf-2-in-target'])] + + def 'Get delta between data nodes for REMOVED data where source data node has #scenario'() { + when: 'attempt to get delta between 2 data nodes' + def result = objectUnderTest.getDeltaReports(sourceDataNodeWithLeafData, []) + then: 'the delta report contains expected "remove" action' + assert result[0].action.equals('remove') + and : 'the delta report contains the expected xpath' + assert result[0].xpath == '/parent' + and: 'the delta report contains expected source data' + assert result[0].sourceData == ['parent-leaf': 'parent-payload-in-source'] + and: 'the delta report contains no target data' + assert result[0].targetData == null + } + + def 'Get delta between data nodes with ADDED data where target data node has #scenario'() { + when: 'attempt to get delta between 2 data nodes' + def result = objectUnderTest.getDeltaReports([], targetDataNodeWithLeafData) + then: 'the delta report contains expected "add" action' + assert result[0].action.equals('add') + and: 'the delta report contains expected xpath' + assert result[0].xpath == '/parent' + and: 'the delta report contains no source data' + assert result[0].sourceData == null + and: 'the delta report contains expected target data' + assert result[0].targetData == ['parent-leaf': 'parent-payload-in-target'] + } + + def 'Delta Report between leaves for parent and child nodes, #scenario'() { + given: 'Two data nodes' + def sourceDataNode = [new DataNode(xpath: '/parent', leaves: ['parent-leaf': 'parent-payload'], childDataNodes: [new DataNode(xpath: '/parent/child', leaves: ['child-leaf': 'child-payload'])])] + def targetDataNode = [new DataNode(xpath: '/parent', leaves: ['parent-leaf': 'parent-payload-updated'], childDataNodes: [new DataNode(xpath: '/parent/child', leaves: ['child-leaf': 'child-payload-updated'])])] + when: 'attempt to get delta between 2 data nodes' + def result = objectUnderTest.getDeltaReports(sourceDataNode, targetDataNode) + then: 'the delta report contains expected "update" action' + assert result[index].action.equals('update') + and: 'the delta report contains expected xpath' + assert result[index].xpath == expectedXpath + and: 'the delta report contains expected source and target data' + assert result[index].sourceData == expectedSourceData + assert result[index].targetData == expectedTargetData + where: 'the following data was used' + scenario | index || expectedXpath | expectedSourceData | expectedTargetData + 'parent data node' | 0 || '/parent' | ['parent-leaf': 'parent-payload'] | ['parent-leaf': 'parent-payload-updated'] + 'child data node' | 1 || '/parent/child' | ['child-leaf': 'child-payload'] | ['child-leaf': 'child-payload-updated'] + } + + def 'Delta report between leaves, #scenario'() { when: 'attempt to get delta between 2 data nodes' - def result = objectUnderTest.getDeltaReports(sourceDataNode as Collection<DataNode>, emptyDataNode) - then: 'the delta report contains "remove" action with right data' - assert result.first().action.equals("remove") - assert result.first().xpath == "/parent/child" - assert result.first().sourceData == expectedSourceData - where: 'following data was used' - scenario | sourceDataNode || expectedSourceData - 'leaf data' | dataNodeWithLeafAndChildDataNode || ['child-leaf': 'child-payload'] - 'no leaf data' | dataNodeWithChildDataNode || null + def result = objectUnderTest.getDeltaReports(sourceDataNode, targetDataNode) + then: 'the delta report contains expected "update" action' + assert result[0].action.equals('update') + and: 'the delta report contains expected xpath' + assert result[0].xpath == '/parent' + and: 'the delta report contains expected source and target data' + assert result[0].sourceData == expectedSourceData + assert result[0].targetData == expectedTargetData + where: 'the following data was used' + scenario | sourceDataNode | targetDataNode || expectedSourceData | expectedTargetData + 'source and target data nodes have leaves' | sourceDataNodeWithLeafData | targetDataNodeWithLeafData || ['parent-leaf': 'parent-payload-in-source'] | ['parent-leaf': 'parent-payload-in-target'] + 'only source data node has leaves' | sourceDataNodeWithLeafData | targetDataNodeWithoutLeafData || ['parent-leaf': 'parent-payload-in-source'] | null + 'only target data node has leaves' | sourceDataNodeWithoutLeafData | targetDataNodeWithLeafData || null | ['parent-leaf': 'parent-payload-in-target'] + 'source and target dsta node with multiple leaves' | sourceDataNodeWithMultipleLeaves | targetDataNodeWithMultipleLeaves || ['leaf-1': 'leaf-1-in-source', 'leaf-2': 'leaf-2-in-source'] | ['leaf-1': 'leaf-1-in-target', 'leaf-2': 'leaf-2-in-target'] } - def 'Get delta between data nodes with new data where target data node has #scenario'() { + def 'Get delta between data nodes for updated data, where source and target data nodes have no leaves '() { when: 'attempt to get delta between 2 data nodes' - def result = objectUnderTest.getDeltaReports(emptyDataNode, targetDataNode) - then: 'the delta report contains "add" action with right data' - assert result.first().action.equals("add") - assert result.first().xpath == "/parent/child" - assert result.first().targetData == expectedTargetData - where: 'following data was used' - scenario | targetDataNode || expectedTargetData - 'leaf data' | dataNodeWithLeafAndChildDataNode || ['child-leaf': 'child-payload'] - 'no leaf data' | dataNodeWithChildDataNode || null + def result = objectUnderTest.getDeltaReports(sourceDataNodeWithoutLeafData, targetDataNodeWithoutLeafData) + then: 'the delta report contains "update" action with right data' + assert result.isEmpty() } } |