org.opendaylight.restconf.server.api.XmlChildBody Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of restconf-nb Show documentation
Show all versions of restconf-nb Show documentation
RESTCONF Northbound application
/*
* Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
package org.opendaylight.restconf.server.api;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import javax.xml.stream.XMLStreamException;
import javax.xml.transform.dom.DOMSource;
import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
import org.opendaylight.yangtools.util.xml.UntrustedXML;
import org.opendaylight.yangtools.yang.common.ErrorTag;
import org.opendaylight.yangtools.yang.common.ErrorType;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.common.XMLNamespace;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
import org.opendaylight.yangtools.yang.data.codec.xml.XmlParserStream;
import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
import org.opendaylight.yangtools.yang.data.impl.schema.NormalizationResultHolder;
import org.opendaylight.yangtools.yang.data.util.DataSchemaContext;
import org.opendaylight.yangtools.yang.data.util.DataSchemaContext.PathMixin;
import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
public final class XmlChildBody extends ChildBody {
private static final Logger LOG = LoggerFactory.getLogger(XmlChildBody.class);
public XmlChildBody(final InputStream inputStream) {
super(inputStream);
}
@Override
@SuppressWarnings("checkstyle:illegalCatch")
PrefixAndBody toPayload(final DatabindPath.Data path, final InputStream inputStream) {
try {
return parse(path, UntrustedXML.newDocumentBuilder().parse(inputStream));
} catch (final RestconfDocumentedException e) {
throw e;
} catch (final Exception e) {
LOG.debug("Error parsing xml input", e);
throwIfYangError(e);
throw new RestconfDocumentedException("Error parsing input: " + e.getMessage(), ErrorType.PROTOCOL,
ErrorTag.MALFORMED_MESSAGE, e);
}
}
private static @NonNull PrefixAndBody parse(final DatabindPath.Data path, final Document doc)
throws XMLStreamException, IOException, SAXException, URISyntaxException {
final var pathInference = path.inference();
final DataSchemaNode parentNode;
if (pathInference.isEmpty()) {
parentNode = pathInference.modelContext();
} else {
final var hackStack = pathInference.toSchemaInferenceStack();
final var hackStmt = hackStack.currentStatement();
if (hackStmt instanceof DataSchemaNode data) {
parentNode = data;
} else {
throw new IllegalStateException("Unknown SchemaNode " + hackStmt);
}
}
var schemaNode = parentNode;
final String docRootElm = doc.getDocumentElement().getLocalName();
final XMLNamespace docRootNamespace = XMLNamespace.of(doc.getDocumentElement().getNamespaceURI());
final var context = pathInference.modelContext();
final var it = context.findModuleStatements(docRootNamespace).iterator();
checkState(it.hasNext(), "Failed to find module for %s", docRootNamespace);
final var qname = QName.create(it.next().localQNameModule(), docRootElm);
final var iiToDataList = ImmutableList.builder();
// FIXME: we should have this readily available: it is the last node the ApiPath->YangInstanceIdentifier parser
// has seen (and it should have the nodeAndStack handy
final var nodeAndStack = path.databind().schemaTree().enterPath(path.instance()).orElseThrow();
final var stack = nodeAndStack.stack();
var current = nodeAndStack.node();
do {
final var next = current instanceof DataSchemaContext.Composite compositeCurrent
? compositeCurrent.enterChild(stack, qname) : null;
if (next == null) {
throw new IllegalStateException(
"Child \"" + qname + "\" was not found in parent schema node \"" + schemaNode + "\"");
}
// Careful about steps: for keyed list items the individual item does not have a PathArgument step,
// as we do not know the key values -- we supply that later
final var step = next.pathStep();
if (step != null) {
iiToDataList.add(step);
}
schemaNode = next.dataSchemaNode();
current = next;
} while (current instanceof PathMixin);
final var resultHolder = new NormalizationResultHolder();
final var writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder);
final var xmlParser = XmlParserStream.create(writer, path.databind().xmlCodecs(), stack.toInference());
xmlParser.traverse(new DOMSource(doc.getDocumentElement()));
var parsed = resultHolder.getResult().data();
// When parsing an XML source with a list root node
// the new XML parser always returns a MapNode with one MapEntryNode inside.
// However, the old XML parser returned a MapEntryNode directly in this place.
// Therefore we now have to extract the MapEntryNode from the parsed MapNode.
if (parsed instanceof MapNode mapNode) {
// extracting the MapEntryNode
parsed = mapNode.body().iterator().next();
}
if (schemaNode instanceof ListSchemaNode) {
// Supply the last item
iiToDataList.add(parsed.name());
}
return new PrefixAndBody(iiToDataList.build(), parsed);
}
}