Fix DataNodeBuilder for ChoiceNodes
[cps.git] / cps-service / src / main / java / org / onap / cps / spi / model / DataNodeBuilder.java
1 /*
2  * ============LICENSE_START=======================================================
3  *  Copyright (C) 2021 Bell Canada. All rights reserved.
4  *  Modifications Copyright (C) 2021 Pantheon.tech
5  *  Modifications Copyright (C) 2022 Nordix Foundation.
6  *  ================================================================================
7  *  Licensed under the Apache License, Version 2.0 (the "License");
8  *  you may not use this file except in compliance with the License.
9  *  You may obtain a copy of the License at
10  *
11  *        http://www.apache.org/licenses/LICENSE-2.0
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  *
18  *  SPDX-License-Identifier: Apache-2.0
19  *  ============LICENSE_END=========================================================
20  */
21
22 package org.onap.cps.spi.model;
23
24 import com.google.common.collect.ImmutableMap;
25 import com.google.common.collect.ImmutableSet;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Set;
31 import java.util.stream.Collectors;
32 import lombok.extern.slf4j.Slf4j;
33 import org.onap.cps.spi.exceptions.DataValidationException;
34 import org.onap.cps.utils.YangUtils;
35 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
36 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
38 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
39 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
40 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
41 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
42 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
43 import org.opendaylight.yangtools.yang.data.api.schema.ValueNode;
44
45 @Slf4j
46 public class DataNodeBuilder {
47
48     private NormalizedNode normalizedNodeTree;
49     private String xpath;
50     private String moduleNamePrefix;
51     private String parentNodeXpath = "";
52     private Map<String, Object> leaves = Collections.emptyMap();
53     private Collection<DataNode> childDataNodes = Collections.emptySet();
54
55     /**
56      * To use parent node xpath for creating {@link DataNode}.
57      *
58      * @param parentNodeXpath xpath of a parent node
59      * @return this {@link DataNodeBuilder} object
60      */
61     public DataNodeBuilder withParentNodeXpath(final String parentNodeXpath) {
62         this.parentNodeXpath = parentNodeXpath;
63         return this;
64     }
65
66
67     /**
68      * To use {@link NormalizedNode} for creating {@link DataNode}.
69      *
70      * @param normalizedNodeTree used for creating the Data Node
71      * @return this {@link DataNodeBuilder} object
72      */
73     public DataNodeBuilder withNormalizedNodeTree(final NormalizedNode normalizedNodeTree) {
74         this.normalizedNodeTree = normalizedNodeTree;
75         return this;
76     }
77
78     /**
79      * To use xpath for creating {@link DataNode}.
80      *
81      * @param xpath for the data node
82      * @return DataNodeBuilder
83      */
84     public DataNodeBuilder withXpath(final String xpath) {
85         this.xpath = xpath;
86         return this;
87     }
88
89     /**
90      * To use module name for prefix for creating {@link DataNode}.
91      *
92      * @param moduleNamePrefix module name as prefix
93      * @return DataNodeBuilder
94      */
95     public DataNodeBuilder withModuleNamePrefix(final String moduleNamePrefix) {
96         this.moduleNamePrefix = moduleNamePrefix;
97         return this;
98     }
99
100     /**
101      * To use attributes for creating {@link DataNode}.
102      *
103      * @param leaves for the data node
104      * @return DataNodeBuilder
105      */
106     public DataNodeBuilder withLeaves(final Map<String, Object> leaves) {
107         this.leaves = leaves;
108         return this;
109     }
110
111     /**
112      * To specify child nodes needs to be used while creating {@link DataNode}.
113      *
114      * @param childDataNodes to be added to the dataNode
115      * @return DataNodeBuilder
116      */
117     public DataNodeBuilder withChildDataNodes(final Collection<DataNode> childDataNodes) {
118         // Added as this is being set from test cases .
119         // Open for suggestions
120         this.childDataNodes = childDataNodes;
121         return this;
122     }
123
124     /**
125      * To create the {@link DataNode}.
126      *
127      * @return {@link DataNode}
128      */
129     public DataNode build() {
130         if (normalizedNodeTree != null) {
131             return buildFromNormalizedNodeTree();
132         } else {
133             return buildFromAttributes();
134         }
135     }
136
137     /**
138      * To build a {@link Collection} of {@link DataNode} objects.
139      *
140      * @return {@link DataNode} {@link Collection}
141      */
142     public Collection<DataNode> buildCollection() {
143         if (normalizedNodeTree != null) {
144             return buildCollectionFromNormalizedNodeTree();
145         } else {
146             return Set.of(buildFromAttributes());
147         }
148     }
149
150     private DataNode buildFromAttributes() {
151         final var dataNode = new DataNode();
152         dataNode.setXpath(xpath);
153         dataNode.setModuleNamePrefix(moduleNamePrefix);
154         dataNode.setLeaves(leaves);
155         dataNode.setChildDataNodes(childDataNodes);
156         return dataNode;
157     }
158
159     private DataNode buildFromNormalizedNodeTree() {
160         final Collection<DataNode> dataNodeCollection = buildCollectionFromNormalizedNodeTree();
161         if (!dataNodeCollection.iterator().hasNext()) {
162             throw new DataValidationException(
163                 "Unsupported xpath: ", "Unsupported xpath as it is referring to one element");
164         }
165         return dataNodeCollection.iterator().next();
166     }
167
168     private Collection<DataNode> buildCollectionFromNormalizedNodeTree() {
169         final var parentDataNode = new DataNodeBuilder().withXpath(parentNodeXpath).build();
170         addDataNodeFromNormalizedNode(parentDataNode, normalizedNodeTree);
171         return parentDataNode.getChildDataNodes();
172     }
173
174     private static void addDataNodeFromNormalizedNode(final DataNode currentDataNode,
175         final NormalizedNode normalizedNode) {
176
177         if (normalizedNode instanceof ChoiceNode) {
178             addChoiceNode(currentDataNode, (ChoiceNode) normalizedNode);
179         } else if (normalizedNode instanceof DataContainerNode) {
180             addYangContainer(currentDataNode, (DataContainerNode) normalizedNode);
181         } else if (normalizedNode instanceof MapNode) {
182             addDataNodeForEachListElement(currentDataNode, (MapNode) normalizedNode);
183         } else if (normalizedNode instanceof ValueNode) {
184             final ValueNode<NormalizedNode> valuesNode = (ValueNode) normalizedNode;
185             addYangLeaf(currentDataNode, valuesNode.getIdentifier().getNodeType().getLocalName(),
186                     valuesNode.body());
187         } else if (normalizedNode instanceof LeafSetNode) {
188             addYangLeafList(currentDataNode, (LeafSetNode<?>) normalizedNode);
189         } else {
190             log.warn("Unsupported NormalizedNode type detected: {}", normalizedNode.getClass());
191         }
192     }
193
194     private static void addYangContainer(final DataNode currentDataNode, final DataContainerNode dataContainerNode) {
195         final DataNode dataContainerDataNode =
196             (dataContainerNode.getIdentifier() instanceof YangInstanceIdentifier.AugmentationIdentifier)
197                 ? currentDataNode
198                 : createAndAddChildDataNode(currentDataNode, YangUtils.buildXpath(dataContainerNode.getIdentifier()));
199         final Collection<DataContainerChild> normalizedChildNodes = dataContainerNode.body();
200         for (final NormalizedNode normalizedNode : normalizedChildNodes) {
201             addDataNodeFromNormalizedNode(dataContainerDataNode, normalizedNode);
202         }
203     }
204
205     private static void addYangLeaf(final DataNode currentDataNode, final String leafName, final Object leafValue) {
206         final Map<String, Object> leaves = new ImmutableMap.Builder<String, Object>()
207             .putAll(currentDataNode.getLeaves())
208             .put(leafName, leafValue)
209             .build();
210         currentDataNode.setLeaves(leaves);
211     }
212
213     private static void addYangLeafList(final DataNode currentDataNode, final LeafSetNode<?> leafSetNode) {
214         final String leafListName = leafSetNode.getIdentifier().getNodeType().getLocalName();
215         final List<?> leafListValues = ((Collection<? extends NormalizedNode>) leafSetNode.body())
216                 .stream()
217                 .map(normalizedNode -> (normalizedNode).body())
218                 .collect(Collectors.toUnmodifiableList());
219         addYangLeaf(currentDataNode, leafListName, leafListValues);
220     }
221
222     private static void addDataNodeForEachListElement(final DataNode currentDataNode, final MapNode mapNode) {
223         final Collection<MapEntryNode> mapEntryNodes = mapNode.body();
224         for (final MapEntryNode mapEntryNode : mapEntryNodes) {
225             addDataNodeFromNormalizedNode(currentDataNode, mapEntryNode);
226         }
227     }
228
229     private static DataNode createAndAddChildDataNode(final DataNode parentDataNode, final String childXpath) {
230
231         final var newChildDataNode = new DataNodeBuilder()
232             .withXpath(parentDataNode.getXpath() + childXpath)
233             .build();
234         final Set<DataNode> allChildDataNodes = new ImmutableSet.Builder<DataNode>()
235             .addAll(parentDataNode.getChildDataNodes())
236             .add(newChildDataNode)
237             .build();
238         parentDataNode.setChildDataNodes(allChildDataNodes);
239         return newChildDataNode;
240     }
241
242     private static void addChoiceNode(final DataNode currentDataNode, final ChoiceNode choiceNode) {
243
244         final Collection<DataContainerChild> normalizedChildNodes = choiceNode.body();
245         for (final NormalizedNode normalizedNode : normalizedChildNodes) {
246             addDataNodeFromNormalizedNode(currentDataNode, normalizedNode);
247         }
248     }
249
250
251 }