2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2024 Nordix Foundation
4 * ================================================================================
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 * SPDX-License-Identifier: Apache-2.0
18 * ============LICENSE_END=========================================================
21 package org.onap.cps.utils;
23 import com.google.gson.JsonSyntaxException;
24 import com.google.gson.stream.JsonReader;
25 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
26 import java.io.IOException;
27 import java.io.StringReader;
28 import java.net.URISyntaxException;
29 import java.util.ArrayList;
30 import java.util.Collection;
31 import java.util.HashMap;
32 import java.util.Iterator;
33 import java.util.List;
35 import javax.xml.parsers.ParserConfigurationException;
36 import javax.xml.stream.XMLInputFactory;
37 import javax.xml.stream.XMLStreamException;
38 import javax.xml.stream.XMLStreamReader;
39 import javax.xml.transform.TransformerException;
40 import lombok.RequiredArgsConstructor;
41 import org.onap.cps.api.exceptions.DataValidationException;
42 import org.onap.cps.cpspath.parser.CpsPathUtil;
43 import org.onap.cps.cpspath.parser.PathParsingException;
44 import org.opendaylight.yangtools.yang.common.QName;
45 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
46 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
47 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
48 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
49 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
50 import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder;
51 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
52 import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactory;
53 import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactorySupplier;
54 import org.opendaylight.yangtools.yang.data.codec.gson.JsonParserStream;
55 import org.opendaylight.yangtools.yang.data.codec.xml.XmlParserStream;
56 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
57 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
58 import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
59 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
60 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
61 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
62 import org.opendaylight.yangtools.yang.model.api.EffectiveStatementInference;
63 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
64 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
65 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
66 import org.springframework.stereotype.Service;
67 import org.xml.sax.SAXException;
70 @RequiredArgsConstructor
71 public class YangParserHelper {
73 static final String DATA_ROOT_NODE_NAMESPACE = "urn:ietf:params:xml:ns:netconf:base:1.0";
74 static final String DATA_ROOT_NODE_TAG_NAME = "data";
75 static final String DATA_VALIDATION_FAILURE_MESSAGE = "Data Validation Failed";
76 static final boolean VALIDATE_ONLY = true;
77 static final boolean VALIDATE_AND_PARSE = false;
80 * Parses data into NormalizedNode according to given schema context.
82 * @param contentType the type of the node data (json or xml)
83 * @param nodeData data string
84 * @param schemaContext schema context describing associated data model
85 * @param parentNodeXpath the xpath referencing the parent node current data fragment belong to
86 * @return the NormalizedNode object
88 public ContainerNode parseData(final ContentType contentType,
89 final String nodeData,
90 final SchemaContext schemaContext,
91 final String parentNodeXpath,
92 final boolean validateOnly) {
93 if (contentType == ContentType.JSON) {
94 final ContainerNode validatedAndParsedJson = parseJsonData(nodeData, schemaContext, parentNodeXpath);
98 return validatedAndParsedJson;
100 final NormalizedNodeResult normalizedNodeResult = parseXmlData(nodeData, schemaContext, parentNodeXpath);
104 return buildContainerNodeFormNormalizedNodeResult(normalizedNodeResult);
107 private ContainerNode parseJsonData(final String jsonData,
108 final SchemaContext schemaContext,
109 final String parentNodeXpath) {
110 final JSONCodecFactory jsonCodecFactory = JSONCodecFactorySupplier.DRAFT_LHOTKA_NETMOD_YANG_JSON_02
111 .getShared((EffectiveModelContext) schemaContext);
112 final DataContainerNodeBuilder<YangInstanceIdentifier.NodeIdentifier, ContainerNode> dataContainerNodeBuilder =
113 Builders.containerBuilder()
114 .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(
115 QName.create(DATA_ROOT_NODE_NAMESPACE, DATA_ROOT_NODE_TAG_NAME)
117 final NormalizedNodeStreamWriter normalizedNodeStreamWriter = ImmutableNormalizedNodeStreamWriter
118 .from(dataContainerNodeBuilder);
119 final JsonReader jsonReader = new JsonReader(new StringReader(jsonData));
120 final JsonParserStream jsonParserStream;
122 if (parentNodeXpath.isEmpty()) {
123 jsonParserStream = JsonParserStream.create(normalizedNodeStreamWriter, jsonCodecFactory);
125 final Collection<QName> dataSchemaNodeIdentifiers
126 = getDataSchemaNodeIdentifiers(schemaContext, parentNodeXpath);
127 final EffectiveModelContext effectiveModelContext = ((EffectiveModelContext) schemaContext);
128 final EffectiveStatementInference effectiveStatementInference =
129 SchemaInferenceStack.of(effectiveModelContext,
130 SchemaNodeIdentifier.Absolute.of(dataSchemaNodeIdentifiers)).toInference();
132 JsonParserStream.create(normalizedNodeStreamWriter, jsonCodecFactory, effectiveStatementInference);
135 try (jsonParserStream) {
136 jsonParserStream.parse(jsonReader);
137 } catch (final IOException | JsonSyntaxException | IllegalStateException | IllegalArgumentException exception) {
138 throw new DataValidationException(
139 DATA_VALIDATION_FAILURE_MESSAGE, "Failed to parse json data. " + exception.getMessage(), exception);
141 return dataContainerNodeBuilder.build();
144 @SuppressFBWarnings(value = "DCN_NULLPOINTER_EXCEPTION", justification = "Problem originates in 3PP code")
145 private NormalizedNodeResult parseXmlData(final String xmlData,
146 final SchemaContext schemaContext,
147 final String parentNodeXpath) {
148 final XMLInputFactory factory = XMLInputFactory.newInstance();
149 factory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
150 final NormalizedNodeResult normalizedNodeResult = new NormalizedNodeResult();
151 final NormalizedNodeStreamWriter normalizedNodeStreamWriter = ImmutableNormalizedNodeStreamWriter
152 .from(normalizedNodeResult);
154 final EffectiveModelContext effectiveModelContext = (EffectiveModelContext) schemaContext;
155 final XmlParserStream xmlParserStream;
156 final String preparedXmlContent;
158 if (parentNodeXpath.isEmpty()) {
159 preparedXmlContent = XmlFileUtils.prepareXmlContent(xmlData, schemaContext);
160 xmlParserStream = XmlParserStream.create(normalizedNodeStreamWriter, effectiveModelContext);
162 final DataSchemaNode parentSchemaNode =
163 (DataSchemaNode) getDataSchemaNodeAndIdentifiersByXpath(parentNodeXpath, schemaContext)
164 .get("dataSchemaNode");
165 final Collection<QName> dataSchemaNodeIdentifiers =
166 getDataSchemaNodeIdentifiers(schemaContext, parentNodeXpath);
167 final EffectiveStatementInference effectiveStatementInference =
168 SchemaInferenceStack.of(effectiveModelContext,
169 SchemaNodeIdentifier.Absolute.of(dataSchemaNodeIdentifiers)).toInference();
170 preparedXmlContent = XmlFileUtils.prepareXmlContent(xmlData, parentSchemaNode, parentNodeXpath);
171 xmlParserStream = XmlParserStream.create(normalizedNodeStreamWriter, effectiveStatementInference);
174 try (xmlParserStream;
175 StringReader stringReader = new StringReader(preparedXmlContent)) {
176 final XMLStreamReader xmlStreamReader = factory.createXMLStreamReader(stringReader);
177 xmlParserStream.parse(xmlStreamReader);
179 } catch (final XMLStreamException | URISyntaxException | IOException | SAXException | NullPointerException
180 | ParserConfigurationException | TransformerException exception) {
181 throw new DataValidationException(
182 DATA_VALIDATION_FAILURE_MESSAGE, "Failed to parse xml data: " + exception.getMessage(), exception);
184 return normalizedNodeResult;
187 private ContainerNode buildContainerNodeFormNormalizedNodeResult(final NormalizedNodeResult normalizedNodeResult) {
189 final DataContainerChild dataContainerChild =
190 (DataContainerChild) getFirstChildXmlRoot(normalizedNodeResult.getResult());
191 final YangInstanceIdentifier.NodeIdentifier nodeIdentifier =
192 new YangInstanceIdentifier.NodeIdentifier(dataContainerChild.getIdentifier().getNodeType());
193 return Builders.containerBuilder().withChild(dataContainerChild).withNodeIdentifier(nodeIdentifier).build();
196 private static Collection<QName> getDataSchemaNodeIdentifiers(final SchemaContext schemaContext,
197 final String parentNodeXpath) {
198 return (Collection<QName>) getDataSchemaNodeAndIdentifiersByXpath(parentNodeXpath, schemaContext)
199 .get("dataSchemaNodeIdentifiers");
202 private static Map<String, Object> getDataSchemaNodeAndIdentifiersByXpath(final String parentNodeXpath,
203 final SchemaContext schemaContext) {
204 final List<String> xpathNodeIdSequence = xpathToNodeIdSequence(parentNodeXpath);
205 return findDataSchemaNodeAndIdentifiersByXpathNodeIdSequence(xpathNodeIdSequence, schemaContext.getChildNodes(),
209 private static List<String> xpathToNodeIdSequence(final String xpath) {
211 return CpsPathUtil.getXpathNodeIdSequence(xpath);
212 } catch (final PathParsingException pathParsingException) {
213 throw new DataValidationException(pathParsingException.getMessage(), pathParsingException.getDetails(),
214 pathParsingException);
218 private static Map<String, Object> findDataSchemaNodeAndIdentifiersByXpathNodeIdSequence(
219 final List<String> xpathNodeIdSequence,
220 final Collection<? extends DataSchemaNode> dataSchemaNodes,
221 final Collection<QName> dataSchemaNodeIdentifiers) {
222 final String currentXpathNodeId = xpathNodeIdSequence.get(0);
223 final DataSchemaNode currentDataSchemaNode = dataSchemaNodes.stream()
224 .filter(dataSchemaNode -> currentXpathNodeId.equals(dataSchemaNode.getQName().getLocalName()))
225 .findFirst().orElseThrow(() -> schemaNodeNotFoundException(currentXpathNodeId));
226 dataSchemaNodeIdentifiers.add(currentDataSchemaNode.getQName());
227 if (xpathNodeIdSequence.size() <= 1) {
228 final Map<String, Object> dataSchemaNodeAndIdentifiers = new HashMap<>();
229 dataSchemaNodeAndIdentifiers.put("dataSchemaNode", currentDataSchemaNode);
230 dataSchemaNodeAndIdentifiers.put("dataSchemaNodeIdentifiers", dataSchemaNodeIdentifiers);
231 return dataSchemaNodeAndIdentifiers;
233 if (currentDataSchemaNode instanceof DataNodeContainer) {
234 return findDataSchemaNodeAndIdentifiersByXpathNodeIdSequence(
235 getNextLevelXpathNodeIdSequence(xpathNodeIdSequence),
236 ((DataNodeContainer) currentDataSchemaNode).getChildNodes(),
237 dataSchemaNodeIdentifiers);
239 throw schemaNodeNotFoundException(xpathNodeIdSequence.get(1));
242 private static List<String> getNextLevelXpathNodeIdSequence(final List<String> xpathNodeIdSequence) {
243 return xpathNodeIdSequence.subList(1, xpathNodeIdSequence.size());
246 private static DataValidationException schemaNodeNotFoundException(final String schemaNodeIdentifier) {
247 return new DataValidationException("Invalid xpath.",
248 String.format("No schema node was found for xpath identifier '%s'.", schemaNodeIdentifier));
251 private static NormalizedNode getFirstChildXmlRoot(final NormalizedNode parent) {
252 final String rootNodeType = parent.getIdentifier().getNodeType().getLocalName();
253 final Collection<DataContainerChild> children = (Collection<DataContainerChild>) parent.body();
254 final Iterator<DataContainerChild> iterator = children.iterator();
255 NormalizedNode child = null;
256 while (iterator.hasNext()) {
257 child = iterator.next();
258 if (!child.getIdentifier().getNodeType().getLocalName().equals(rootNodeType)
259 && !(child instanceof LeafNode)) {
263 return getFirstChildXmlRoot(child);