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.server.api;
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.yangtools.util.xml.UntrustedXML;
21 import org.opendaylight.yangtools.yang.common.ErrorTag;
22 import org.opendaylight.yangtools.yang.common.ErrorType;
23 import org.opendaylight.yangtools.yang.common.QName;
24 import org.opendaylight.yangtools.yang.common.XMLNamespace;
25 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
26 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
27 import org.opendaylight.yangtools.yang.data.codec.xml.XmlParserStream;
28 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
29 import org.opendaylight.yangtools.yang.data.impl.schema.NormalizationResultHolder;
30 import org.opendaylight.yangtools.yang.data.util.DataSchemaContext;
31 import org.opendaylight.yangtools.yang.data.util.DataSchemaContext.PathMixin;
32 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36 import org.w3c.dom.Document;
37 import org.xml.sax.SAXException;
39 public final class XmlChildBody extends ChildBody {
40 private static final Logger LOG = LoggerFactory.getLogger(XmlChildBody.class);
42 public XmlChildBody(final InputStream inputStream) {
47 @SuppressWarnings("checkstyle:illegalCatch")
48 PrefixAndBody toPayload(final DatabindPath.Data path, final InputStream inputStream) {
50 return parse(path, UntrustedXML.newDocumentBuilder().parse(inputStream));
51 } catch (final RestconfDocumentedException e) {
53 } catch (final Exception e) {
54 LOG.debug("Error parsing xml input", e);
56 throw new RestconfDocumentedException("Error parsing input: " + e.getMessage(), ErrorType.PROTOCOL,
57 ErrorTag.MALFORMED_MESSAGE, e);
61 private static @NonNull PrefixAndBody parse(final DatabindPath.Data path, final Document doc)
62 throws XMLStreamException, IOException, SAXException, URISyntaxException {
63 final var pathInference = path.inference();
65 final DataSchemaNode parentNode;
66 if (pathInference.isEmpty()) {
67 parentNode = pathInference.modelContext();
69 final var hackStack = pathInference.toSchemaInferenceStack();
70 final var hackStmt = hackStack.currentStatement();
71 if (hackStmt instanceof DataSchemaNode data) {
74 throw new IllegalStateException("Unknown SchemaNode " + hackStmt);
78 var schemaNode = parentNode;
79 final String docRootElm = doc.getDocumentElement().getLocalName();
80 final XMLNamespace docRootNamespace = XMLNamespace.of(doc.getDocumentElement().getNamespaceURI());
81 final var context = pathInference.modelContext();
82 final var it = context.findModuleStatements(docRootNamespace).iterator();
83 checkState(it.hasNext(), "Failed to find module for %s", docRootNamespace);
84 final var qname = QName.create(it.next().localQNameModule(), docRootElm);
86 final var iiToDataList = ImmutableList.<PathArgument>builder();
87 // FIXME: we should have this readily available: it is the last node the ApiPath->YangInstanceIdentifier parser
88 // has seen (and it should have the nodeAndStack handy
89 final var nodeAndStack = path.databind().schemaTree().enterPath(path.instance()).orElseThrow();
90 final var stack = nodeAndStack.stack();
91 var current = nodeAndStack.node();
93 final var next = current instanceof DataSchemaContext.Composite compositeCurrent
94 ? compositeCurrent.enterChild(stack, qname) : null;
96 throw new IllegalStateException(
97 "Child \"" + qname + "\" was not found in parent schema node \"" + schemaNode + "\"");
100 // Careful about steps: for keyed list items the individual item does not have a PathArgument step,
101 // as we do not know the key values -- we supply that later
102 final var step = next.pathStep();
104 iiToDataList.add(step);
106 schemaNode = next.dataSchemaNode();
108 } while (current instanceof PathMixin);
110 final var resultHolder = new NormalizationResultHolder();
111 final var writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
113 final var xmlParser = XmlParserStream.create(writer, path.databind().xmlCodecs(), stack.toInference());
114 xmlParser.traverse(new DOMSource(doc.getDocumentElement()));
115 var parsed = resultHolder.getResult().data();
117 // When parsing an XML source with a list root node
118 // the new XML parser always returns a MapNode with one MapEntryNode inside.
119 // However, the old XML parser returned a MapEntryNode directly in this place.
120 // Therefore we now have to extract the MapEntryNode from the parsed MapNode.
121 if (parsed instanceof MapNode mapNode) {
122 // extracting the MapEntryNode
123 parsed = mapNode.body().iterator().next();
126 if (schemaNode instanceof ListSchemaNode) {
127 // Supply the last item
128 iiToDataList.add(parsed.name());
131 return new PrefixAndBody(iiToDataList.build(), parsed);