2 * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.restconf.nb.rfc8040.databind;
10 import static com.google.common.base.Preconditions.checkState;
12 import com.google.common.collect.ImmutableList;
13 import java.io.IOException;
14 import java.io.InputStream;
15 import java.net.URISyntaxException;
16 import javax.xml.stream.XMLStreamException;
17 import javax.xml.transform.dom.DOMSource;
18 import org.eclipse.jdt.annotation.NonNull;
19 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
20 import org.opendaylight.restconf.server.api.DataPostPath;
21 import org.opendaylight.yangtools.util.xml.UntrustedXML;
22 import org.opendaylight.yangtools.yang.common.ErrorTag;
23 import org.opendaylight.yangtools.yang.common.ErrorType;
24 import org.opendaylight.yangtools.yang.common.QName;
25 import org.opendaylight.yangtools.yang.common.XMLNamespace;
26 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
27 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
28 import org.opendaylight.yangtools.yang.data.codec.xml.XmlParserStream;
29 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
30 import org.opendaylight.yangtools.yang.data.impl.schema.NormalizationResultHolder;
31 import org.opendaylight.yangtools.yang.data.util.DataSchemaContext;
32 import org.opendaylight.yangtools.yang.data.util.DataSchemaContext.PathMixin;
33 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
34 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37 import org.w3c.dom.Document;
38 import org.xml.sax.SAXException;
40 public final class XmlChildBody extends ChildBody {
41 private static final Logger LOG = LoggerFactory.getLogger(XmlChildBody.class);
43 public XmlChildBody(final InputStream inputStream) {
48 @SuppressWarnings("checkstyle:illegalCatch")
49 PrefixAndBody toPayload(final DataPostPath path, final InputStream inputStream) {
51 return parse(path, UntrustedXML.newDocumentBuilder().parse(inputStream));
52 } catch (final RestconfDocumentedException e) {
54 } catch (final Exception e) {
55 LOG.debug("Error parsing xml input", e);
57 throw new RestconfDocumentedException("Error parsing input: " + e.getMessage(), ErrorType.PROTOCOL,
58 ErrorTag.MALFORMED_MESSAGE, e);
62 private static @NonNull PrefixAndBody parse(final DataPostPath path, final Document doc)
63 throws XMLStreamException, IOException, SAXException, URISyntaxException {
64 final var pathInference = path.inference();
66 final DataSchemaNode parentNode;
67 if (pathInference.isEmpty()) {
68 parentNode = pathInference.modelContext();
70 final var hackStack = pathInference.toSchemaInferenceStack();
71 final var hackStmt = hackStack.currentStatement();
72 if (hackStmt instanceof DataSchemaNode data) {
75 throw new IllegalStateException("Unknown SchemaNode " + hackStmt);
79 var schemaNode = parentNode;
80 final String docRootElm = doc.getDocumentElement().getLocalName();
81 final XMLNamespace docRootNamespace = XMLNamespace.of(doc.getDocumentElement().getNamespaceURI());
82 final var context = pathInference.modelContext();
83 final var it = context.findModuleStatements(docRootNamespace).iterator();
84 checkState(it.hasNext(), "Failed to find module for %s", docRootNamespace);
85 final var qname = QName.create(it.next().localQNameModule(), docRootElm);
87 final var iiToDataList = ImmutableList.<PathArgument>builder();
88 // FIXME: we should have this readily available: it is the last node the ApiPath->YangInstanceIdentifier parser
89 // has seen (and it should have the nodeAndStack handy
90 final var nodeAndStack = path.databind().schemaTree().enterPath(path.instance()).orElseThrow();
91 final var stack = nodeAndStack.stack();
92 var current = nodeAndStack.node();
94 final var next = current instanceof DataSchemaContext.Composite compositeCurrent
95 ? compositeCurrent.enterChild(stack, qname) : null;
97 throw new IllegalStateException(
98 "Child \"" + qname + "\" was not found in parent schema node \"" + schemaNode + "\"");
101 // Careful about steps: for keyed list items the individual item does not have a PathArgument step,
102 // as we do not know the key values -- we supply that later
103 final var step = next.pathStep();
105 iiToDataList.add(step);
107 schemaNode = next.dataSchemaNode();
109 } while (current instanceof PathMixin);
111 final var resultHolder = new NormalizationResultHolder();
112 final var writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
114 final var xmlParser = XmlParserStream.create(writer, path.databind().xmlCodecs(), stack.toInference());
115 xmlParser.traverse(new DOMSource(doc.getDocumentElement()));
116 var parsed = resultHolder.getResult().data();
118 // When parsing an XML source with a list root node
119 // the new XML parser always returns a MapNode with one MapEntryNode inside.
120 // However, the old XML parser returned a MapEntryNode directly in this place.
121 // Therefore we now have to extract the MapEntryNode from the parsed MapNode.
122 if (parsed instanceof MapNode mapNode) {
123 // extracting the MapEntryNode
124 parsed = mapNode.body().iterator().next();
127 if (schemaNode instanceof ListSchemaNode) {
128 // Supply the last item
129 iiToDataList.add(parsed.name());
132 return new PrefixAndBody(iiToDataList.build(), parsed);