From d07d069303845144d5cad64197a6535207c721a9 Mon Sep 17 00:00:00 2001 From: leventecsanyi Date: Mon, 20 Jan 2025 21:58:19 +0100 Subject: Moved builders under impl in CPS Service - moved builders and fixed the Arch test Issue-ID: CPS-2542 Change-Id: Iac9d47f20fff2e6fa415ada8bc17880b0b032591 Signed-off-by: leventecsanyi --- .../main/java/org/onap/cps/api/model/DataNode.java | 5 +- .../org/onap/cps/api/model/DataNodeBuilder.java | 284 -------------------- .../java/org/onap/cps/api/model/DeltaReport.java | 5 +- .../org/onap/cps/api/model/DeltaReportBuilder.java | 84 ------ .../java/org/onap/cps/impl/CpsDataServiceImpl.java | 1 - .../org/onap/cps/impl/CpsDeltaServiceImpl.java | 1 - .../java/org/onap/cps/impl/DataNodeBuilder.java | 285 +++++++++++++++++++++ .../java/org/onap/cps/impl/DeltaReportBuilder.java | 85 ++++++ .../onap/cps/api/model/DataNodeBuilderSpec.groovy | 246 ------------------ .../cps/api/model/DeltaReportBuilderSpec.groovy | 53 ---- .../onap/cps/impl/CpsDataServiceImplSpec.groovy | 1 - .../org/onap/cps/impl/DataNodeBuilderSpec.groovy | 247 ++++++++++++++++++ .../onap/cps/impl/DeltaReportBuilderSpec.groovy | 53 ++++ .../org/onap/cps/utils/DataMapUtilsSpec.groovy | 2 +- 14 files changed, 675 insertions(+), 677 deletions(-) delete mode 100644 cps-service/src/main/java/org/onap/cps/api/model/DataNodeBuilder.java delete mode 100644 cps-service/src/main/java/org/onap/cps/api/model/DeltaReportBuilder.java create mode 100644 cps-service/src/main/java/org/onap/cps/impl/DataNodeBuilder.java create mode 100644 cps-service/src/main/java/org/onap/cps/impl/DeltaReportBuilder.java delete mode 100644 cps-service/src/test/groovy/org/onap/cps/api/model/DataNodeBuilderSpec.groovy delete mode 100644 cps-service/src/test/groovy/org/onap/cps/api/model/DeltaReportBuilderSpec.groovy create mode 100644 cps-service/src/test/groovy/org/onap/cps/impl/DataNodeBuilderSpec.groovy create mode 100644 cps-service/src/test/groovy/org/onap/cps/impl/DeltaReportBuilderSpec.groovy (limited to 'cps-service') diff --git a/cps-service/src/main/java/org/onap/cps/api/model/DataNode.java b/cps-service/src/main/java/org/onap/cps/api/model/DataNode.java index be80b636ad..be559709f8 100644 --- a/cps-service/src/main/java/org/onap/cps/api/model/DataNode.java +++ b/cps-service/src/main/java/org/onap/cps/api/model/DataNode.java @@ -26,19 +26,18 @@ import java.io.Serializable; import java.util.Collection; import java.util.Collections; import java.util.Map; -import lombok.AccessLevel; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; -@Setter(AccessLevel.PROTECTED) +@Setter @Getter @EqualsAndHashCode public class DataNode implements Serializable { private static final long serialVersionUID = 1482619410918597467L; - DataNode() {} + public DataNode() {} private String dataspace; private String schemaSetName; diff --git a/cps-service/src/main/java/org/onap/cps/api/model/DataNodeBuilder.java b/cps-service/src/main/java/org/onap/cps/api/model/DataNodeBuilder.java deleted file mode 100644 index d509f53525..0000000000 --- a/cps-service/src/main/java/org/onap/cps/api/model/DataNodeBuilder.java +++ /dev/null @@ -1,284 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2021 Bell Canada. All rights reserved. - * Modifications Copyright (C) 2021 Pantheon.tech - * Modifications Copyright (C) 2022-2024 Nordix Foundation. - * Modifications Copyright (C) 2022-2023 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.api.model; - -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import java.io.Serializable; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; -import lombok.extern.slf4j.Slf4j; -import org.onap.cps.api.exceptions.DataValidationException; -import org.onap.cps.utils.YangUtils; -import org.opendaylight.yangtools.yang.common.Ordering; -import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; -import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode; -import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; -import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; -import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode; -import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode; -import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; -import org.opendaylight.yangtools.yang.data.api.schema.MapNode; -import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; -import org.opendaylight.yangtools.yang.data.api.schema.ValueNode; - -@Slf4j -public class DataNodeBuilder { - - private ContainerNode containerNode; - private String xpath; - private String moduleNamePrefix; - private String parentNodeXpath = ""; - private Map leaves = Collections.emptyMap(); - private Collection childDataNodes = Collections.emptySet(); - private String dataspaceName; - private String anchorName; - - /** - * To use parent node xpath for creating {@link DataNode}. - * - * @param parentNodeXpath xpath of a parent node - * @return this {@link DataNodeBuilder} object - */ - public DataNodeBuilder withParentNodeXpath(final String parentNodeXpath) { - this.parentNodeXpath = parentNodeXpath; - return this; - } - - /** - * To use {@link Collection} of Normalized Nodes for creating {@link DataNode}. - * - * @param containerNode used for creating the Data Node - * @return this {@link DataNodeBuilder} object - */ - public DataNodeBuilder withContainerNode(final ContainerNode containerNode) { - this.containerNode = containerNode; - return this; - } - - /** - * To use xpath for creating {@link DataNode}. - * - * @param xpath for the data node - * @return DataNodeBuilder - */ - public DataNodeBuilder withXpath(final String xpath) { - this.xpath = xpath; - return this; - } - - /** - * To use dataspace name for creating {@link DataNode}. - * - * @param dataspaceName dataspace name for the data node - * @return DataNodeBuilder - */ - public DataNodeBuilder withDataspace(final String dataspaceName) { - this.dataspaceName = dataspaceName; - return this; - } - - /** - * To use anchor name for creating {@link DataNode}. - * - * @param anchorName anchor name for the data node - * @return DataNodeBuilder - */ - public DataNodeBuilder withAnchor(final String anchorName) { - this.anchorName = anchorName; - return this; - } - - /** - * To use module name for prefix for creating {@link DataNode}. - * - * @param moduleNamePrefix module name as prefix - * @return DataNodeBuilder - */ - public DataNodeBuilder withModuleNamePrefix(final String moduleNamePrefix) { - this.moduleNamePrefix = moduleNamePrefix; - return this; - } - - /** - * To use attributes for creating {@link DataNode}. - * - * @param leaves for the data node - * @return DataNodeBuilder - */ - public DataNodeBuilder withLeaves(final Map leaves) { - this.leaves = leaves; - return this; - } - - /** - * To specify child nodes needs to be used while creating {@link DataNode}. - * - * @param childDataNodes to be added to the dataNode - * @return DataNodeBuilder - */ - public DataNodeBuilder withChildDataNodes(final Collection childDataNodes) { - // Added as this is being set from test cases . - // Open for suggestions - this.childDataNodes = childDataNodes; - return this; - } - - /** - * To create the {@link DataNode}. - * - * @return {@link DataNode} - */ - public DataNode build() { - if (containerNode != null) { - return buildFromContainerNode(); - } - return buildFromAttributes(); - } - - /** - * To build a {@link Collection} of {@link DataNode} objects. - * - * @return {@link DataNode} {@link Collection} - */ - public Collection buildCollection() { - if (containerNode != null) { - return buildCollectionFromContainerNode(); - } - return Collections.emptySet(); - } - - private DataNode buildFromAttributes() { - final var dataNode = new DataNode(); - dataNode.setXpath(xpath); - dataNode.setModuleNamePrefix(moduleNamePrefix); - dataNode.setLeaves(leaves); - dataNode.setChildDataNodes(childDataNodes); - dataNode.setDataspace(dataspaceName); - dataNode.setAnchorName(anchorName); - return dataNode; - } - - private DataNode buildFromContainerNode() { - final Collection dataNodeCollection = buildCollectionFromContainerNode(); - if (dataNodeCollection.isEmpty()) { - throw new DataValidationException("Unsupported Normalized Node", "No valid node found"); - } - return dataNodeCollection.iterator().next(); - } - - private Collection buildCollectionFromContainerNode() { - final var parentDataNode = new DataNodeBuilder().withXpath(parentNodeXpath).build(); - if (containerNode.body() != null) { - for (final NormalizedNode normalizedNode: containerNode.body()) { - addDataNodeFromNormalizedNode(parentDataNode, normalizedNode); - } - } - return parentDataNode.getChildDataNodes(); - } - - private static void addDataNodeFromNormalizedNode(final DataNode currentDataNode, - final NormalizedNode normalizedNode) { - - if (normalizedNode instanceof ChoiceNode choiceNode) { - addChoiceNode(currentDataNode, choiceNode); - } else if (normalizedNode instanceof DataContainerNode dataContainerNode) { - addYangContainer(currentDataNode, dataContainerNode); - } else if (normalizedNode instanceof MapNode mapNode) { - addDataNodeForEachListElement(currentDataNode, mapNode); - } else if (normalizedNode instanceof ValueNode valueNode) { - addYangLeaf(currentDataNode, valueNode.getIdentifier().getNodeType().getLocalName(), - (Serializable) valueNode.body()); - } else if (normalizedNode instanceof LeafSetNode leafSetNode) { - addYangLeafList(currentDataNode, leafSetNode); - } else { - log.warn("Unsupported NormalizedNode type detected: {}", normalizedNode.getClass()); - } - } - - private static void addYangContainer(final DataNode currentDataNode, final DataContainerNode dataContainerNode) { - final DataNode dataContainerDataNode = - (dataContainerNode.getIdentifier() instanceof YangInstanceIdentifier.AugmentationIdentifier) - ? currentDataNode - : createAndAddChildDataNode(currentDataNode, YangUtils.buildXpath(dataContainerNode.getIdentifier())); - final Collection normalizedChildNodes = dataContainerNode.body(); - for (final NormalizedNode normalizedNode : normalizedChildNodes) { - addDataNodeFromNormalizedNode(dataContainerDataNode, normalizedNode); - } - } - - private static void addYangLeaf(final DataNode currentDataNode, final String leafName, - final Serializable leafValue) { - final Map leaves = new ImmutableMap.Builder() - .putAll(currentDataNode.getLeaves()) - .put(leafName, leafValue) - .build(); - currentDataNode.setLeaves(leaves); - } - - private static void addYangLeafList(final DataNode currentDataNode, final LeafSetNode leafSetNode) { - final String leafListName = leafSetNode.getIdentifier().getNodeType().getLocalName(); - List leafListValues = (leafSetNode.body()) - .stream() - .map(NormalizedNode::body) - .collect(Collectors.toList()); - if (leafSetNode.ordering() == Ordering.SYSTEM) { - leafListValues.sort(null); - } - leafListValues = Collections.unmodifiableList(leafListValues); - addYangLeaf(currentDataNode, leafListName, (Serializable) leafListValues); - } - - private static void addDataNodeForEachListElement(final DataNode currentDataNode, final MapNode mapNode) { - final Collection mapEntryNodes = mapNode.body(); - for (final MapEntryNode mapEntryNode : mapEntryNodes) { - addDataNodeFromNormalizedNode(currentDataNode, mapEntryNode); - } - } - - private static DataNode createAndAddChildDataNode(final DataNode parentDataNode, final String childXpath) { - - final var newChildDataNode = new DataNodeBuilder() - .withXpath(parentDataNode.getXpath() + childXpath) - .build(); - final Set allChildDataNodes = new ImmutableSet.Builder() - .addAll(parentDataNode.getChildDataNodes()) - .add(newChildDataNode) - .build(); - parentDataNode.setChildDataNodes(allChildDataNodes); - return newChildDataNode; - } - - private static void addChoiceNode(final DataNode currentDataNode, final ChoiceNode choiceNode) { - - final Collection normalizedChildNodes = choiceNode.body(); - for (final NormalizedNode normalizedNode : normalizedChildNodes) { - addDataNodeFromNormalizedNode(currentDataNode, normalizedNode); - } - } - -} diff --git a/cps-service/src/main/java/org/onap/cps/api/model/DeltaReport.java b/cps-service/src/main/java/org/onap/cps/api/model/DeltaReport.java index df642628d0..77d8d771c1 100644 --- a/cps-service/src/main/java/org/onap/cps/api/model/DeltaReport.java +++ b/cps-service/src/main/java/org/onap/cps/api/model/DeltaReport.java @@ -23,11 +23,10 @@ package org.onap.cps.api.model; import com.fasterxml.jackson.annotation.JsonInclude; import java.io.Serializable; import java.util.Map; -import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; -@Setter(AccessLevel.PROTECTED) +@Setter @Getter @JsonInclude(JsonInclude.Include.NON_NULL) public class DeltaReport { @@ -36,7 +35,7 @@ public class DeltaReport { public static final String REMOVE_ACTION = "remove"; public static final String REPLACE_ACTION = "replace"; - DeltaReport() {} + public DeltaReport() {} private String action; private String xpath; diff --git a/cps-service/src/main/java/org/onap/cps/api/model/DeltaReportBuilder.java b/cps-service/src/main/java/org/onap/cps/api/model/DeltaReportBuilder.java deleted file mode 100644 index a8e922f3df..0000000000 --- a/cps-service/src/main/java/org/onap/cps/api/model/DeltaReportBuilder.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2023 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.api.model; - -import java.io.Serializable; -import java.util.Map; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -public class DeltaReportBuilder { - - - private String action; - private String xpath; - private Map sourceData; - private Map targetData; - - public DeltaReportBuilder withXpath(final String xpath) { - this.xpath = xpath; - return this; - } - - public DeltaReportBuilder withSourceData(final Map sourceData) { - this.sourceData = sourceData; - return this; - } - - public DeltaReportBuilder withTargetData(final Map targetData) { - this.targetData = targetData; - return this; - } - - public DeltaReportBuilder actionCreate() { - this.action = DeltaReport.CREATE_ACTION; - return this; - } - - public DeltaReportBuilder actionRemove() { - this.action = DeltaReport.REMOVE_ACTION; - return this; - } - - public DeltaReportBuilder actionReplace() { - this.action = DeltaReport.REPLACE_ACTION; - return this; - } - - /** - * To create a single entry of {@link DeltaReport}. - * - * @return {@link DeltaReport} - */ - public DeltaReport build() { - final DeltaReport deltaReport = new DeltaReport(); - deltaReport.setAction(action); - deltaReport.setXpath(xpath); - if (sourceData != null && !sourceData.isEmpty()) { - deltaReport.setSourceData(sourceData); - } - - if (targetData != null && !targetData.isEmpty()) { - deltaReport.setTargetData(targetData); - } - return deltaReport; - } -} diff --git a/cps-service/src/main/java/org/onap/cps/impl/CpsDataServiceImpl.java b/cps-service/src/main/java/org/onap/cps/impl/CpsDataServiceImpl.java index f2513173a6..653fd4803f 100644 --- a/cps-service/src/main/java/org/onap/cps/impl/CpsDataServiceImpl.java +++ b/cps-service/src/main/java/org/onap/cps/impl/CpsDataServiceImpl.java @@ -42,7 +42,6 @@ import org.onap.cps.api.CpsDeltaService; import org.onap.cps.api.exceptions.DataValidationException; import org.onap.cps.api.model.Anchor; import org.onap.cps.api.model.DataNode; -import org.onap.cps.api.model.DataNodeBuilder; import org.onap.cps.api.model.DeltaReport; import org.onap.cps.api.parameters.FetchDescendantsOption; import org.onap.cps.cpspath.parser.CpsPathUtil; 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 7a9d142506..d532001aec 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 @@ -32,7 +32,6 @@ import lombok.extern.slf4j.Slf4j; import org.onap.cps.api.CpsDeltaService; import org.onap.cps.api.model.DataNode; import org.onap.cps.api.model.DeltaReport; -import org.onap.cps.api.model.DeltaReportBuilder; import org.springframework.stereotype.Service; @Slf4j diff --git a/cps-service/src/main/java/org/onap/cps/impl/DataNodeBuilder.java b/cps-service/src/main/java/org/onap/cps/impl/DataNodeBuilder.java new file mode 100644 index 0000000000..a78f3d9826 --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/impl/DataNodeBuilder.java @@ -0,0 +1,285 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Bell Canada. All rights reserved. + * Modifications Copyright (C) 2021 Pantheon.tech + * Modifications Copyright (C) 2022-2024 Nordix Foundation. + * Modifications Copyright (C) 2022-2023 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.impl; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.api.exceptions.DataValidationException; +import org.onap.cps.api.model.DataNode; +import org.onap.cps.utils.YangUtils; +import org.opendaylight.yangtools.yang.common.Ordering; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; +import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode; +import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.MapNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.ValueNode; + +@Slf4j +public class DataNodeBuilder { + + private ContainerNode containerNode; + private String xpath; + private String moduleNamePrefix; + private String parentNodeXpath = ""; + private Map leaves = Collections.emptyMap(); + private Collection childDataNodes = Collections.emptySet(); + private String dataspaceName; + private String anchorName; + + /** + * To use parent node xpath for creating {@link DataNode}. + * + * @param parentNodeXpath xpath of a parent node + * @return this {@link DataNodeBuilder} object + */ + public DataNodeBuilder withParentNodeXpath(final String parentNodeXpath) { + this.parentNodeXpath = parentNodeXpath; + return this; + } + + /** + * To use {@link Collection} of Normalized Nodes for creating {@link DataNode}. + * + * @param containerNode used for creating the Data Node + * @return this {@link DataNodeBuilder} object + */ + public DataNodeBuilder withContainerNode(final ContainerNode containerNode) { + this.containerNode = containerNode; + return this; + } + + /** + * To use xpath for creating {@link DataNode}. + * + * @param xpath for the data node + * @return DataNodeBuilder + */ + public DataNodeBuilder withXpath(final String xpath) { + this.xpath = xpath; + return this; + } + + /** + * To use dataspace name for creating {@link DataNode}. + * + * @param dataspaceName dataspace name for the data node + * @return DataNodeBuilder + */ + public DataNodeBuilder withDataspace(final String dataspaceName) { + this.dataspaceName = dataspaceName; + return this; + } + + /** + * To use anchor name for creating {@link DataNode}. + * + * @param anchorName anchor name for the data node + * @return DataNodeBuilder + */ + public DataNodeBuilder withAnchor(final String anchorName) { + this.anchorName = anchorName; + return this; + } + + /** + * To use module name for prefix for creating {@link DataNode}. + * + * @param moduleNamePrefix module name as prefix + * @return DataNodeBuilder + */ + public DataNodeBuilder withModuleNamePrefix(final String moduleNamePrefix) { + this.moduleNamePrefix = moduleNamePrefix; + return this; + } + + /** + * To use attributes for creating {@link DataNode}. + * + * @param leaves for the data node + * @return DataNodeBuilder + */ + public DataNodeBuilder withLeaves(final Map leaves) { + this.leaves = leaves; + return this; + } + + /** + * To specify child nodes needs to be used while creating {@link DataNode}. + * + * @param childDataNodes to be added to the dataNode + * @return DataNodeBuilder + */ + public DataNodeBuilder withChildDataNodes(final Collection childDataNodes) { + // Added as this is being set from test cases . + // Open for suggestions + this.childDataNodes = childDataNodes; + return this; + } + + /** + * To create the {@link DataNode}. + * + * @return {@link DataNode} + */ + public DataNode build() { + if (containerNode != null) { + return buildFromContainerNode(); + } + return buildFromAttributes(); + } + + /** + * To build a {@link Collection} of {@link DataNode} objects. + * + * @return {@link DataNode} {@link Collection} + */ + public Collection buildCollection() { + if (containerNode != null) { + return buildCollectionFromContainerNode(); + } + return Collections.emptySet(); + } + + private DataNode buildFromAttributes() { + final var dataNode = new DataNode(); + dataNode.setXpath(xpath); + dataNode.setModuleNamePrefix(moduleNamePrefix); + dataNode.setLeaves(leaves); + dataNode.setChildDataNodes(childDataNodes); + dataNode.setDataspace(dataspaceName); + dataNode.setAnchorName(anchorName); + return dataNode; + } + + private DataNode buildFromContainerNode() { + final Collection dataNodeCollection = buildCollectionFromContainerNode(); + if (dataNodeCollection.isEmpty()) { + throw new DataValidationException("Unsupported Normalized Node", "No valid node found"); + } + return dataNodeCollection.iterator().next(); + } + + private Collection buildCollectionFromContainerNode() { + final var parentDataNode = new DataNodeBuilder().withXpath(parentNodeXpath).build(); + if (containerNode.body() != null) { + for (final NormalizedNode normalizedNode: containerNode.body()) { + addDataNodeFromNormalizedNode(parentDataNode, normalizedNode); + } + } + return parentDataNode.getChildDataNodes(); + } + + private static void addDataNodeFromNormalizedNode(final DataNode currentDataNode, + final NormalizedNode normalizedNode) { + + if (normalizedNode instanceof ChoiceNode choiceNode) { + addChoiceNode(currentDataNode, choiceNode); + } else if (normalizedNode instanceof DataContainerNode dataContainerNode) { + addYangContainer(currentDataNode, dataContainerNode); + } else if (normalizedNode instanceof MapNode mapNode) { + addDataNodeForEachListElement(currentDataNode, mapNode); + } else if (normalizedNode instanceof ValueNode valueNode) { + addYangLeaf(currentDataNode, valueNode.getIdentifier().getNodeType().getLocalName(), + (Serializable) valueNode.body()); + } else if (normalizedNode instanceof LeafSetNode leafSetNode) { + addYangLeafList(currentDataNode, leafSetNode); + } else { + log.warn("Unsupported NormalizedNode type detected: {}", normalizedNode.getClass()); + } + } + + private static void addYangContainer(final DataNode currentDataNode, final DataContainerNode dataContainerNode) { + final DataNode dataContainerDataNode = + (dataContainerNode.getIdentifier() instanceof YangInstanceIdentifier.AugmentationIdentifier) + ? currentDataNode + : createAndAddChildDataNode(currentDataNode, YangUtils.buildXpath(dataContainerNode.getIdentifier())); + final Collection normalizedChildNodes = dataContainerNode.body(); + for (final NormalizedNode normalizedNode : normalizedChildNodes) { + addDataNodeFromNormalizedNode(dataContainerDataNode, normalizedNode); + } + } + + private static void addYangLeaf(final DataNode currentDataNode, final String leafName, + final Serializable leafValue) { + final Map leaves = new ImmutableMap.Builder() + .putAll(currentDataNode.getLeaves()) + .put(leafName, leafValue) + .build(); + currentDataNode.setLeaves(leaves); + } + + private static void addYangLeafList(final DataNode currentDataNode, final LeafSetNode leafSetNode) { + final String leafListName = leafSetNode.getIdentifier().getNodeType().getLocalName(); + List leafListValues = (leafSetNode.body()) + .stream() + .map(NormalizedNode::body) + .collect(Collectors.toList()); + if (leafSetNode.ordering() == Ordering.SYSTEM) { + leafListValues.sort(null); + } + leafListValues = Collections.unmodifiableList(leafListValues); + addYangLeaf(currentDataNode, leafListName, (Serializable) leafListValues); + } + + private static void addDataNodeForEachListElement(final DataNode currentDataNode, final MapNode mapNode) { + final Collection mapEntryNodes = mapNode.body(); + for (final MapEntryNode mapEntryNode : mapEntryNodes) { + addDataNodeFromNormalizedNode(currentDataNode, mapEntryNode); + } + } + + private static DataNode createAndAddChildDataNode(final DataNode parentDataNode, final String childXpath) { + + final var newChildDataNode = new DataNodeBuilder() + .withXpath(parentDataNode.getXpath() + childXpath) + .build(); + final Set allChildDataNodes = new ImmutableSet.Builder() + .addAll(parentDataNode.getChildDataNodes()) + .add(newChildDataNode) + .build(); + parentDataNode.setChildDataNodes(allChildDataNodes); + return newChildDataNode; + } + + private static void addChoiceNode(final DataNode currentDataNode, final ChoiceNode choiceNode) { + + final Collection normalizedChildNodes = choiceNode.body(); + for (final NormalizedNode normalizedNode : normalizedChildNodes) { + addDataNodeFromNormalizedNode(currentDataNode, normalizedNode); + } + } + +} diff --git a/cps-service/src/main/java/org/onap/cps/impl/DeltaReportBuilder.java b/cps-service/src/main/java/org/onap/cps/impl/DeltaReportBuilder.java new file mode 100644 index 0000000000..fdc2e939d6 --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/impl/DeltaReportBuilder.java @@ -0,0 +1,85 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 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.impl; + +import java.io.Serializable; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.api.model.DeltaReport; + +@Slf4j +public class DeltaReportBuilder { + + + private String action; + private String xpath; + private Map sourceData; + private Map targetData; + + public DeltaReportBuilder withXpath(final String xpath) { + this.xpath = xpath; + return this; + } + + public DeltaReportBuilder withSourceData(final Map sourceData) { + this.sourceData = sourceData; + return this; + } + + public DeltaReportBuilder withTargetData(final Map targetData) { + this.targetData = targetData; + return this; + } + + public DeltaReportBuilder actionCreate() { + this.action = DeltaReport.CREATE_ACTION; + return this; + } + + public DeltaReportBuilder actionRemove() { + this.action = DeltaReport.REMOVE_ACTION; + return this; + } + + public DeltaReportBuilder actionReplace() { + this.action = DeltaReport.REPLACE_ACTION; + return this; + } + + /** + * To create a single entry of {@link DeltaReport}. + * + * @return {@link DeltaReport} + */ + public DeltaReport build() { + final DeltaReport deltaReport = new DeltaReport(); + deltaReport.setAction(action); + deltaReport.setXpath(xpath); + if (sourceData != null && !sourceData.isEmpty()) { + deltaReport.setSourceData(sourceData); + } + + if (targetData != null && !targetData.isEmpty()) { + deltaReport.setTargetData(targetData); + } + return deltaReport; + } +} diff --git a/cps-service/src/test/groovy/org/onap/cps/api/model/DataNodeBuilderSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/model/DataNodeBuilderSpec.groovy deleted file mode 100644 index 24c78864a5..0000000000 --- a/cps-service/src/test/groovy/org/onap/cps/api/model/DataNodeBuilderSpec.groovy +++ /dev/null @@ -1,246 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2021 Pantheon.tech - * Modifications Copyright (C) 2021-2024 Nordix Foundation. - * Modifications Copyright (C) 2022 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.api.model - -import org.onap.cps.TestUtils -import org.onap.cps.api.exceptions.DataValidationException -import org.onap.cps.utils.ContentType -import org.onap.cps.utils.DataMapUtils -import org.onap.cps.utils.YangParserHelper -import org.onap.cps.yang.YangTextSchemaSourceSetBuilder -import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode -import org.opendaylight.yangtools.yang.data.api.schema.ForeignDataNode -import spock.lang.Specification - -class DataNodeBuilderSpec extends Specification { - - def objectUnderTest = new DataNodeBuilder() - def yangParserHelper = new YangParserHelper() - def validateAndParse = false - - def expectedLeavesByXpathMap = [ - '/test-tree' : [], - '/test-tree/branch[@name=\'Left\']' : [name: 'Left'], - '/test-tree/branch[@name=\'Left\']/nest' : [name: 'Small', birds: ['Sparrow', 'Robin', 'Finch']], - '/test-tree/branch[@name=\'Right\']' : [name: 'Right'], - '/test-tree/branch[@name=\'Right\']/nest' : [name: 'Big', birds: ['Owl', 'Raven', 'Crow']], - '/test-tree/fruit[@color=\'Green\' and @name=\'Apple\']': [color: 'Green', name: 'Apple'] - ] - - String[] networkTopologyModelRfc8345 = [ - 'ietf/ietf-yang-types@2013-07-15.yang', - 'ietf/ietf-network-topology-state@2018-02-26.yang', - 'ietf/ietf-network-topology@2018-02-26.yang', - 'ietf/ietf-network-state@2018-02-26.yang', - 'ietf/ietf-network@2018-02-26.yang', - 'ietf/ietf-inet-types@2013-07-15.yang' - ] - - def 'Converting ContainerNode (tree) to a DataNode (tree).'() { - given: 'the schema context for expected model' - def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('test-tree.yang') - def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext() - and: 'the json data parsed into container node object' - def jsonData = TestUtils.getResourceFileContent('test-tree.json') - def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '', validateAndParse) - when: 'the container node is converted to a data node' - def result = objectUnderTest.withContainerNode(containerNode).build() - def mappedResult = TestUtils.getFlattenMapByXpath(result) - then: '6 DataNode objects with unique xpath were created in total' - mappedResult.size() == 6 - and: 'all expected xpaths were built' - mappedResult.keySet().containsAll(expectedLeavesByXpathMap.keySet()) - and: 'each data node contains the expected attributes' - mappedResult.each { - xpath, dataNode -> assertLeavesMaps(dataNode.getLeaves(), expectedLeavesByXpathMap[xpath]) - } - } - - def 'Converting ContainerNode (tree) to a DataNode (tree) for known parent node.'() { - given: 'a schema context for expected model' - def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('test-tree.yang') - def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext() - and: 'the json data parsed into container node object' - def jsonData = '{ "branch": [{ "name": "Branch", "nest": { "name": "Nest", "birds": ["bird"] } }] }' - def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '/test-tree', validateAndParse) - when: 'the container node is converted to a data node with parent node xpath defined' - def result = objectUnderTest.withContainerNode(containerNode).withParentNodeXpath('/test-tree').build() - def mappedResult = TestUtils.getFlattenMapByXpath(result) - then: '2 DataNode objects with unique xpath were created in total' - mappedResult.size() == 2 - and: 'all expected xpaths were built' - mappedResult.keySet().containsAll(['/test-tree/branch[@name=\'Branch\']', '/test-tree/branch[@name=\'Branch\']/nest']) - } - - def 'Converting ContainerNode (tree) to a DataNode (tree) -- augmentation case.'() { - given: 'a schema context for expected model' - def yangResourceNameToContent = TestUtils.getYangResourcesAsMap(networkTopologyModelRfc8345) - def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext() - and: 'the json data parsed into container node object' - def jsonData = TestUtils.getResourceFileContent('ietf/data/ietf-network-topology-sample-rfc8345.json') - def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '', validateAndParse) - when: 'the container node is converted to a data node ' - def result = objectUnderTest.withContainerNode(containerNode).build() - def mappedResult = TestUtils.getFlattenMapByXpath(result) - then: 'all expected data nodes are populated' - mappedResult.size() == 32 - and: 'xpaths for augmentation nodes (link and termination-point nodes) were built correctly' - mappedResult.keySet().containsAll([ - "/networks/network[@network-id='otn-hc']/link[@link-id='D1,1-2-1,D2,2-1-1']", - "/networks/network[@network-id='otn-hc']/link[@link-id='D1,1-3-1,D3,3-1-1']", - "/networks/network[@network-id='otn-hc']/link[@link-id='D2,2-1-1,D1,1-2-1']", - "/networks/network[@network-id='otn-hc']/link[@link-id='D2,2-3-1,D3,3-2-1']", - "/networks/network[@network-id='otn-hc']/link[@link-id='D3,3-1-1,D1,1-3-1']", - "/networks/network[@network-id='otn-hc']/link[@link-id='D3,3-2-1,D2,2-3-1']", - "/networks/network[@network-id='otn-hc']/node[@node-id='D1']/termination-point[@tp-id='1-0-1']", - "/networks/network[@network-id='otn-hc']/node[@node-id='D1']/termination-point[@tp-id='1-2-1']", - "/networks/network[@network-id='otn-hc']/node[@node-id='D1']/termination-point[@tp-id='1-3-1']", - "/networks/network[@network-id='otn-hc']/node[@node-id='D2']/termination-point[@tp-id='2-0-1']", - "/networks/network[@network-id='otn-hc']/node[@node-id='D2']/termination-point[@tp-id='2-1-1']", - "/networks/network[@network-id='otn-hc']/node[@node-id='D2']/termination-point[@tp-id='2-3-1']", - "/networks/network[@network-id='otn-hc']/node[@node-id='D3']/termination-point[@tp-id='3-1-1']", - "/networks/network[@network-id='otn-hc']/node[@node-id='D3']/termination-point[@tp-id='3-2-1']" - ]) - } - - def 'Converting ContainerNode (tree) to a DataNode (tree) for known parent node -- augmentation case.'() { - given: 'a schema context for expected model' - def yangResourceNameToContent = TestUtils.getYangResourcesAsMap(networkTopologyModelRfc8345) - def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext() - and: 'parent node xpath referencing augmentation node within a model' - def parentNodeXpath = "/networks/network[@network-id='otn-hc']/link[@link-id='D1,1-2-1,D2,2-1-1']" - and: 'the json data fragment parsed into container node object for given parent node xpath' - def jsonData = '{"source": {"source-node": "D1", "source-tp": "1-2-1"}}' - def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext,parentNodeXpath, validateAndParse) - when: 'the container node is converted to a data node with given parent node xpath' - def result = objectUnderTest.withContainerNode(containerNode).withParentNodeXpath(parentNodeXpath).build() - then: 'the resulting data node represents a child of augmentation node' - assert result.xpath == "/networks/network[@network-id='otn-hc']/link[@link-id='D1,1-2-1,D2,2-1-1']/source" - assert result.leaves['source-node'] == 'D1' - assert result.leaves['source-tp'] == '1-2-1' - } - - def 'Converting ContainerNode (tree) to a DataNode (tree) -- with ChoiceNode.'() { - given: 'a schema context for expected model' - def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('yang-with-choice-node.yang') - def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext() - and: 'the json data fragment parsed into container node object' - def jsonData = TestUtils.getResourceFileContent('data-with-choice-node.json') - def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '', validateAndParse) - when: 'the container node is converted to a data node' - def result = objectUnderTest.withContainerNode(containerNode).build() - def mappedResult = TestUtils.getFlattenMapByXpath(result) - then: 'the resulting data node contains only one xpath with 3 leaves' - mappedResult.keySet().containsAll([ '/container-with-choice-leaves' ]) - assert result.leaves['leaf-1'] == 'test' - assert result.leaves['choice-case1-leaf-a'] == 'test' - assert result.leaves['choice-case1-leaf-b'] == 'test' - } - - def 'Converting ContainerNode into DataNode collection: #scenario.'() { - given: 'a schema context for expected model' - def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('test-tree.yang') - def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext() - and: 'parent node xpath referencing parent of list element' - def parentNodeXpath = '/test-tree' - and: 'the json data fragment (list element) parsed into container node object' - def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, parentNodeXpath, validateAndParse) - when: 'the container node is converted to a data node collection' - def result = objectUnderTest.withContainerNode(containerNode).withParentNodeXpath(parentNodeXpath).buildCollection() - def resultXpaths = result.collect { it.getXpath() } - then: 'the resulting collection contains data nodes for expected list elements' - assert resultXpaths.size() == expectedSize - assert resultXpaths.containsAll(expectedXpaths) - where: 'following parameters are used' - scenario | jsonData | expectedSize | expectedXpaths - 'single entry' | '{"branch": [{"name": "One"}]}' | 1 | ['/test-tree/branch[@name=\'One\']'] - 'multiple entries' | '{"branch": [{"name": "One"}, {"name": "Two"}]}' | 2 | ['/test-tree/branch[@name=\'One\']', '/test-tree/branch[@name=\'Two\']'] - } - - def 'Converting ContainerNode to a Collection with #scenario.'() { - expect: 'converting null to a collection returns an empty collection' - assert objectUnderTest.withContainerNode(containerNode).buildCollection().isEmpty() - where: 'the following container node is used' - scenario | containerNode - 'null object' | null - 'object without body' | Mock(ContainerNode) - } - - def 'Converting ContainerNode to a DataNode with unsupported Normalized Node.'() { - given: 'a container node of an unsupported type' - def mockContainerNode = Mock(ContainerNode) - mockContainerNode.body() >> [ Mock(ForeignDataNode) ] - when: 'attempt to convert it' - objectUnderTest.withContainerNode(mockContainerNode).build() - then: 'a data validation exception is thrown' - thrown(DataValidationException) - } - - def 'Build datanode from attributes.'() { - when: 'data node is built' - def result = new DataNodeBuilder() - .withDataspace('my dataspace') - .withAnchor('my anchor') - .withModuleNamePrefix('my prefix') - .withXpath('some xpath') - .withLeaves([leaf1: 'value1']) - .withChildDataNodes([Mock(DataNode)]) - .build() - then: 'the datanode has all the defined attributes' - assert result.dataspace == 'my dataspace' - assert result.anchorName == 'my anchor' - assert result.moduleNamePrefix == 'my prefix' - assert result.moduleNamePrefix == 'my prefix' - assert result.xpath == 'some xpath' - assert result.leaves == [leaf1: 'value1'] - assert result.childDataNodes.size() == 1 - } - - def 'Use of adding the module name prefix attribute of data node.'() { - when: 'data node is built with a prefix' - def testDataNode = new DataNodeBuilder() - .withXpath(xPath) - .withLeaves(sampleLeaves) - .build() - then: 'the result when node request is a #scenario includes the correct prefix' - def result = new DataMapUtils().toDataMapWithIdentifier(testDataNode, 'sampleModuleNamePrefix') - result.toString() == expectedResult - where: 'the following parameters are used' - scenario | xPath | sampleLeaves | expectedResult - 'list attribute' | '/test-tree/branch[@name=\'Right\']/nest' | [name: 'Big', birds: ['Owl']] | '{sampleModuleNamePrefix:nest={name=Big, birds=[Owl]}}' - 'container xpath' | '/test-tree/branch[@name=\'Left\']' | [name: 'Left'] | '{sampleModuleNamePrefix:branch={name=Left}}' - } - - def static assertLeavesMaps(actualLeavesMap, expectedLeavesMap) { - expectedLeavesMap.each { key, value -> - { - def actualValue = actualLeavesMap[key] - if (value instanceof Collection && actualValue instanceof Collection) { - assert value.size() == actualValue.size() - assert value.containsAll(actualValue) - } else { - assert value == actualValue - } - } - } - } -} diff --git a/cps-service/src/test/groovy/org/onap/cps/api/model/DeltaReportBuilderSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/model/DeltaReportBuilderSpec.groovy deleted file mode 100644 index 94e3ed5c26..0000000000 --- a/cps-service/src/test/groovy/org/onap/cps/api/model/DeltaReportBuilderSpec.groovy +++ /dev/null @@ -1,53 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2023 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.api.model - - -import spock.lang.Specification - -class DeltaReportBuilderSpec extends Specification{ - - def 'Generating delta report for "create" action'() { - when: 'delta report is generated' - def result = new DeltaReportBuilder() - .actionCreate() - .withXpath('/xpath') - .withTargetData(['data':'leaf-data']) - .build() - then: 'the delta report contains the "create" action with expected target data' - assert result.action == 'create' - assert result.xpath == '/xpath' - assert result.targetData == ['data': 'leaf-data'] - } - - def 'Generating delta report with attributes for "remove" action'() { - when: 'delta report is generated' - def result = new DeltaReportBuilder() - .actionRemove() - .withXpath('/xpath') - .withSourceData(['data':'leaf-data']) - .build() - then: 'the delta report contains the "remove" action with expected source data' - assert result.action == 'remove' - assert result.xpath == '/xpath' - assert result.sourceData == ['data': 'leaf-data'] - } -} diff --git a/cps-service/src/test/groovy/org/onap/cps/impl/CpsDataServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/impl/CpsDataServiceImplSpec.groovy index 3ea859ae6d..a828d26991 100644 --- a/cps-service/src/test/groovy/org/onap/cps/impl/CpsDataServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/impl/CpsDataServiceImplSpec.groovy @@ -40,7 +40,6 @@ import org.onap.cps.api.exceptions.DataValidationException import org.onap.cps.api.exceptions.SessionManagerException import org.onap.cps.api.exceptions.SessionTimeoutException import org.onap.cps.api.model.Anchor -import org.onap.cps.api.model.DataNodeBuilder import org.onap.cps.utils.ContentType import org.onap.cps.utils.JsonObjectMapper import org.onap.cps.utils.PrefixResolver diff --git a/cps-service/src/test/groovy/org/onap/cps/impl/DataNodeBuilderSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/impl/DataNodeBuilderSpec.groovy new file mode 100644 index 0000000000..1597d45761 --- /dev/null +++ b/cps-service/src/test/groovy/org/onap/cps/impl/DataNodeBuilderSpec.groovy @@ -0,0 +1,247 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Pantheon.tech + * Modifications Copyright (C) 2021-2024 Nordix Foundation. + * Modifications Copyright (C) 2022 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.impl + +import org.onap.cps.TestUtils +import org.onap.cps.api.exceptions.DataValidationException +import org.onap.cps.api.model.DataNode +import org.onap.cps.utils.ContentType +import org.onap.cps.utils.DataMapUtils +import org.onap.cps.utils.YangParserHelper +import org.onap.cps.yang.YangTextSchemaSourceSetBuilder +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode +import org.opendaylight.yangtools.yang.data.api.schema.ForeignDataNode +import spock.lang.Specification + +class DataNodeBuilderSpec extends Specification { + + def objectUnderTest = new DataNodeBuilder() + def yangParserHelper = new YangParserHelper() + def validateAndParse = false + + def expectedLeavesByXpathMap = [ + '/test-tree' : [], + '/test-tree/branch[@name=\'Left\']' : [name: 'Left'], + '/test-tree/branch[@name=\'Left\']/nest' : [name: 'Small', birds: ['Sparrow', 'Robin', 'Finch']], + '/test-tree/branch[@name=\'Right\']' : [name: 'Right'], + '/test-tree/branch[@name=\'Right\']/nest' : [name: 'Big', birds: ['Owl', 'Raven', 'Crow']], + '/test-tree/fruit[@color=\'Green\' and @name=\'Apple\']': [color: 'Green', name: 'Apple'] + ] + + String[] networkTopologyModelRfc8345 = [ + 'ietf/ietf-yang-types@2013-07-15.yang', + 'ietf/ietf-network-topology-state@2018-02-26.yang', + 'ietf/ietf-network-topology@2018-02-26.yang', + 'ietf/ietf-network-state@2018-02-26.yang', + 'ietf/ietf-network@2018-02-26.yang', + 'ietf/ietf-inet-types@2013-07-15.yang' + ] + + def 'Converting ContainerNode (tree) to a DataNode (tree).'() { + given: 'the schema context for expected model' + def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('test-tree.yang') + def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext() + and: 'the json data parsed into container node object' + def jsonData = TestUtils.getResourceFileContent('test-tree.json') + def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '', validateAndParse) + when: 'the container node is converted to a data node' + def result = objectUnderTest.withContainerNode(containerNode).build() + def mappedResult = TestUtils.getFlattenMapByXpath(result) + then: '6 DataNode objects with unique xpath were created in total' + mappedResult.size() == 6 + and: 'all expected xpaths were built' + mappedResult.keySet().containsAll(expectedLeavesByXpathMap.keySet()) + and: 'each data node contains the expected attributes' + mappedResult.each { + xpath, dataNode -> assertLeavesMaps(dataNode.getLeaves(), expectedLeavesByXpathMap[xpath]) + } + } + + def 'Converting ContainerNode (tree) to a DataNode (tree) for known parent node.'() { + given: 'a schema context for expected model' + def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('test-tree.yang') + def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext() + and: 'the json data parsed into container node object' + def jsonData = '{ "branch": [{ "name": "Branch", "nest": { "name": "Nest", "birds": ["bird"] } }] }' + def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '/test-tree', validateAndParse) + when: 'the container node is converted to a data node with parent node xpath defined' + def result = objectUnderTest.withContainerNode(containerNode).withParentNodeXpath('/test-tree').build() + def mappedResult = TestUtils.getFlattenMapByXpath(result) + then: '2 DataNode objects with unique xpath were created in total' + mappedResult.size() == 2 + and: 'all expected xpaths were built' + mappedResult.keySet().containsAll(['/test-tree/branch[@name=\'Branch\']', '/test-tree/branch[@name=\'Branch\']/nest']) + } + + def 'Converting ContainerNode (tree) to a DataNode (tree) -- augmentation case.'() { + given: 'a schema context for expected model' + def yangResourceNameToContent = TestUtils.getYangResourcesAsMap(networkTopologyModelRfc8345) + def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext() + and: 'the json data parsed into container node object' + def jsonData = TestUtils.getResourceFileContent('ietf/data/ietf-network-topology-sample-rfc8345.json') + def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '', validateAndParse) + when: 'the container node is converted to a data node ' + def result = objectUnderTest.withContainerNode(containerNode).build() + def mappedResult = TestUtils.getFlattenMapByXpath(result) + then: 'all expected data nodes are populated' + mappedResult.size() == 32 + and: 'xpaths for augmentation nodes (link and termination-point nodes) were built correctly' + mappedResult.keySet().containsAll([ + "/networks/network[@network-id='otn-hc']/link[@link-id='D1,1-2-1,D2,2-1-1']", + "/networks/network[@network-id='otn-hc']/link[@link-id='D1,1-3-1,D3,3-1-1']", + "/networks/network[@network-id='otn-hc']/link[@link-id='D2,2-1-1,D1,1-2-1']", + "/networks/network[@network-id='otn-hc']/link[@link-id='D2,2-3-1,D3,3-2-1']", + "/networks/network[@network-id='otn-hc']/link[@link-id='D3,3-1-1,D1,1-3-1']", + "/networks/network[@network-id='otn-hc']/link[@link-id='D3,3-2-1,D2,2-3-1']", + "/networks/network[@network-id='otn-hc']/node[@node-id='D1']/termination-point[@tp-id='1-0-1']", + "/networks/network[@network-id='otn-hc']/node[@node-id='D1']/termination-point[@tp-id='1-2-1']", + "/networks/network[@network-id='otn-hc']/node[@node-id='D1']/termination-point[@tp-id='1-3-1']", + "/networks/network[@network-id='otn-hc']/node[@node-id='D2']/termination-point[@tp-id='2-0-1']", + "/networks/network[@network-id='otn-hc']/node[@node-id='D2']/termination-point[@tp-id='2-1-1']", + "/networks/network[@network-id='otn-hc']/node[@node-id='D2']/termination-point[@tp-id='2-3-1']", + "/networks/network[@network-id='otn-hc']/node[@node-id='D3']/termination-point[@tp-id='3-1-1']", + "/networks/network[@network-id='otn-hc']/node[@node-id='D3']/termination-point[@tp-id='3-2-1']" + ]) + } + + def 'Converting ContainerNode (tree) to a DataNode (tree) for known parent node -- augmentation case.'() { + given: 'a schema context for expected model' + def yangResourceNameToContent = TestUtils.getYangResourcesAsMap(networkTopologyModelRfc8345) + def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext() + and: 'parent node xpath referencing augmentation node within a model' + def parentNodeXpath = "/networks/network[@network-id='otn-hc']/link[@link-id='D1,1-2-1,D2,2-1-1']" + and: 'the json data fragment parsed into container node object for given parent node xpath' + def jsonData = '{"source": {"source-node": "D1", "source-tp": "1-2-1"}}' + def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext,parentNodeXpath, validateAndParse) + when: 'the container node is converted to a data node with given parent node xpath' + def result = objectUnderTest.withContainerNode(containerNode).withParentNodeXpath(parentNodeXpath).build() + then: 'the resulting data node represents a child of augmentation node' + assert result.xpath == "/networks/network[@network-id='otn-hc']/link[@link-id='D1,1-2-1,D2,2-1-1']/source" + assert result.leaves['source-node'] == 'D1' + assert result.leaves['source-tp'] == '1-2-1' + } + + def 'Converting ContainerNode (tree) to a DataNode (tree) -- with ChoiceNode.'() { + given: 'a schema context for expected model' + def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('yang-with-choice-node.yang') + def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext() + and: 'the json data fragment parsed into container node object' + def jsonData = TestUtils.getResourceFileContent('data-with-choice-node.json') + def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, '', validateAndParse) + when: 'the container node is converted to a data node' + def result = objectUnderTest.withContainerNode(containerNode).build() + def mappedResult = TestUtils.getFlattenMapByXpath(result) + then: 'the resulting data node contains only one xpath with 3 leaves' + mappedResult.keySet().containsAll([ '/container-with-choice-leaves' ]) + assert result.leaves['leaf-1'] == 'test' + assert result.leaves['choice-case1-leaf-a'] == 'test' + assert result.leaves['choice-case1-leaf-b'] == 'test' + } + + def 'Converting ContainerNode into DataNode collection: #scenario.'() { + given: 'a schema context for expected model' + def yangResourceNameToContent = TestUtils.getYangResourcesAsMap('test-tree.yang') + def schemaContext = YangTextSchemaSourceSetBuilder.of(yangResourceNameToContent) getSchemaContext() + and: 'parent node xpath referencing parent of list element' + def parentNodeXpath = '/test-tree' + and: 'the json data fragment (list element) parsed into container node object' + def containerNode = yangParserHelper.parseData(ContentType.JSON, jsonData, schemaContext, parentNodeXpath, validateAndParse) + when: 'the container node is converted to a data node collection' + def result = objectUnderTest.withContainerNode(containerNode).withParentNodeXpath(parentNodeXpath).buildCollection() + def resultXpaths = result.collect { it.getXpath() } + then: 'the resulting collection contains data nodes for expected list elements' + assert resultXpaths.size() == expectedSize + assert resultXpaths.containsAll(expectedXpaths) + where: 'following parameters are used' + scenario | jsonData | expectedSize | expectedXpaths + 'single entry' | '{"branch": [{"name": "One"}]}' | 1 | ['/test-tree/branch[@name=\'One\']'] + 'multiple entries' | '{"branch": [{"name": "One"}, {"name": "Two"}]}' | 2 | ['/test-tree/branch[@name=\'One\']', '/test-tree/branch[@name=\'Two\']'] + } + + def 'Converting ContainerNode to a Collection with #scenario.'() { + expect: 'converting null to a collection returns an empty collection' + assert objectUnderTest.withContainerNode(containerNode).buildCollection().isEmpty() + where: 'the following container node is used' + scenario | containerNode + 'null object' | null + 'object without body' | Mock(ContainerNode) + } + + def 'Converting ContainerNode to a DataNode with unsupported Normalized Node.'() { + given: 'a container node of an unsupported type' + def mockContainerNode = Mock(ContainerNode) + mockContainerNode.body() >> [ Mock(ForeignDataNode) ] + when: 'attempt to convert it' + objectUnderTest.withContainerNode(mockContainerNode).build() + then: 'a data validation exception is thrown' + thrown(DataValidationException) + } + + def 'Build datanode from attributes.'() { + when: 'data node is built' + def result = new DataNodeBuilder() + .withDataspace('my dataspace') + .withAnchor('my anchor') + .withModuleNamePrefix('my prefix') + .withXpath('some xpath') + .withLeaves([leaf1: 'value1']) + .withChildDataNodes([Mock(DataNode)]) + .build() + then: 'the datanode has all the defined attributes' + assert result.dataspace == 'my dataspace' + assert result.anchorName == 'my anchor' + assert result.moduleNamePrefix == 'my prefix' + assert result.moduleNamePrefix == 'my prefix' + assert result.xpath == 'some xpath' + assert result.leaves == [leaf1: 'value1'] + assert result.childDataNodes.size() == 1 + } + + def 'Use of adding the module name prefix attribute of data node.'() { + when: 'data node is built with a prefix' + def testDataNode = new DataNodeBuilder() + .withXpath(xPath) + .withLeaves(sampleLeaves) + .build() + then: 'the result when node request is a #scenario includes the correct prefix' + def result = new DataMapUtils().toDataMapWithIdentifier(testDataNode, 'sampleModuleNamePrefix') + result.toString() == expectedResult + where: 'the following parameters are used' + scenario | xPath | sampleLeaves | expectedResult + 'list attribute' | '/test-tree/branch[@name=\'Right\']/nest' | [name: 'Big', birds: ['Owl']] | '{sampleModuleNamePrefix:nest={name=Big, birds=[Owl]}}' + 'container xpath' | '/test-tree/branch[@name=\'Left\']' | [name: 'Left'] | '{sampleModuleNamePrefix:branch={name=Left}}' + } + + def static assertLeavesMaps(actualLeavesMap, expectedLeavesMap) { + expectedLeavesMap.each { key, value -> + { + def actualValue = actualLeavesMap[key] + if (value instanceof Collection && actualValue instanceof Collection) { + assert value.size() == actualValue.size() + assert value.containsAll(actualValue) + } else { + assert value == actualValue + } + } + } + } +} diff --git a/cps-service/src/test/groovy/org/onap/cps/impl/DeltaReportBuilderSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/impl/DeltaReportBuilderSpec.groovy new file mode 100644 index 0000000000..2decefff21 --- /dev/null +++ b/cps-service/src/test/groovy/org/onap/cps/impl/DeltaReportBuilderSpec.groovy @@ -0,0 +1,53 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 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.impl + + +import spock.lang.Specification + +class DeltaReportBuilderSpec extends Specification{ + + def 'Generating delta report for "create" action'() { + when: 'delta report is generated' + def result = new DeltaReportBuilder() + .actionCreate() + .withXpath('/xpath') + .withTargetData(['data':'leaf-data']) + .build() + then: 'the delta report contains the "create" action with expected target data' + assert result.action == 'create' + assert result.xpath == '/xpath' + assert result.targetData == ['data': 'leaf-data'] + } + + def 'Generating delta report with attributes for "remove" action'() { + when: 'delta report is generated' + def result = new DeltaReportBuilder() + .actionRemove() + .withXpath('/xpath') + .withSourceData(['data':'leaf-data']) + .build() + then: 'the delta report contains the "remove" action with expected source data' + assert result.action == 'remove' + assert result.xpath == '/xpath' + assert result.sourceData == ['data': 'leaf-data'] + } +} diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/DataMapUtilsSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/DataMapUtilsSpec.groovy index bb0f5b0911..6ff41c128f 100644 --- a/cps-service/src/test/groovy/org/onap/cps/utils/DataMapUtilsSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/utils/DataMapUtilsSpec.groovy @@ -22,7 +22,7 @@ package org.onap.cps.utils -import org.onap.cps.api.model.DataNodeBuilder +import org.onap.cps.impl.DataNodeBuilder import spock.lang.Specification class DataMapUtilsSpec extends Specification { -- cgit