From 588fa01e1777c9191d8f29f9624834c271ca5fd7 Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Mon, 20 May 2019 08:19:18 +0200 Subject: [PATCH] Add support for opaque anydata XML input This patch adds preliminary support for streaming anydata nodes into opaque values. JIRA: YANGTOOLS-991 Change-Id: Iebe67aaf32cc7dde6c54541e8036f9a47544f573 Signed-off-by: Robert Varga --- .../yang/data/codec/xml/XmlParserStream.java | 97 +++++++++++++++++- .../yang/data/codec/xml/AnydataParseTest.java | 53 ++++++++++ .../AbstractAnydataNodeDataWithSchema.java | 47 +++++++++ .../util/OpaqueAnydataNodeDataWithSchema.java | 98 +++++++++++++++++++ 4 files changed, 291 insertions(+), 4 deletions(-) create mode 100644 yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/AnydataParseTest.java create mode 100644 yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/AbstractAnydataNodeDataWithSchema.java create mode 100644 yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/OpaqueAnydataNodeDataWithSchema.java diff --git a/yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/XmlParserStream.java b/yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/XmlParserStream.java index 80958a3a0e..8db667bd73 100644 --- a/yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/XmlParserStream.java +++ b/yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/XmlParserStream.java @@ -42,10 +42,16 @@ import javax.xml.transform.TransformerFactoryConfigurationError; import javax.xml.transform.dom.DOMResult; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stax.StAXSource; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; import org.opendaylight.yangtools.odlext.model.api.YangModeledAnyXmlSchemaNode; +import org.opendaylight.yangtools.rfc7952.data.util.AbstractImmutableOpaqueAnydataStreamWriter; +import org.opendaylight.yangtools.rfc7952.data.util.ImmutableNormalizedMetadata; import org.opendaylight.yangtools.rfc7952.model.api.AnnotationSchemaNode; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.common.QNameModule; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.opaque.OpaqueData; import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; import org.opendaylight.yangtools.yang.data.util.AbstractNodeDataWithSchema; import org.opendaylight.yangtools.yang.data.util.AnyXmlNodeDataWithSchema; @@ -56,10 +62,12 @@ import org.opendaylight.yangtools.yang.data.util.LeafListNodeDataWithSchema; import org.opendaylight.yangtools.yang.data.util.LeafNodeDataWithSchema; import org.opendaylight.yangtools.yang.data.util.ListEntryNodeDataWithSchema; import org.opendaylight.yangtools.yang.data.util.ListNodeDataWithSchema; +import org.opendaylight.yangtools.yang.data.util.OpaqueAnydataNodeDataWithSchema; import org.opendaylight.yangtools.yang.data.util.OperationAsContainer; import org.opendaylight.yangtools.yang.data.util.ParserStreamUtils; import org.opendaylight.yangtools.yang.data.util.SimpleNodeDataWithSchema; import org.opendaylight.yangtools.yang.data.util.YangModeledAnyXmlNodeDataWithSchema; +import org.opendaylight.yangtools.yang.model.api.AnyDataSchemaNode; import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode; import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; @@ -230,6 +238,8 @@ public final class XmlParserStream implements Closeable, Flushable { nodeDataWithSchema = new LeafNodeDataWithSchema((LeafSchemaNode) parentNode); } else if (parentNode instanceof LeafListSchemaNode) { nodeDataWithSchema = new LeafListNodeDataWithSchema((LeafListSchemaNode) parentNode); + } else if (parentNode instanceof AnyDataSchemaNode) { + nodeDataWithSchema = new OpaqueAnydataNodeDataWithSchema((AnyDataSchemaNode) parentNode); } else { throw new IllegalStateException("Unsupported schema node type " + parentNode.getClass() + "."); } @@ -331,6 +341,45 @@ public final class XmlParserStream implements Closeable, Flushable { return (Document) result.getNode(); } + private void readOpaqueAnydataValue(final XMLStreamReader in, final OpaqueAnydataNodeDataWithSchema parent) + throws XMLStreamException { + final DefaultOpaqueAnydataStreamWriter opaqueWriter = new DefaultOpaqueAnydataStreamWriter(); + final Entry result; + while (true) { + final int event = in.next(); + try { + switch (event) { + case XMLStreamConstants.START_ELEMENT: + final String nsUri = in.getNamespaceURI(); + final QNameModule module = resolveXmlNamespace(nsUri).orElseGet(() -> rawXmlNamespace(nsUri)); + opaqueWriter.startOpaqueContainer(NodeIdentifier.create(QName.create(module, + in.getLocalName()))); + break; + case XMLStreamConstants.END_ELEMENT: + opaqueWriter.endOpaqueNode(); + break; + case XMLStreamConstants.CHARACTERS: + opaqueWriter.opaqueValue(in.getText()); + break; + default: + LOG.debug("Ignoring event {}", event); + continue; + } + } catch (IOException e) { + throw new XMLStreamException("Inconsistent anydata stream", e); + } + + final Entry optResult = opaqueWriter.result(); + if (optResult != null) { + result = optResult; + break; + } + } + + setValue(parent, result.getKey(), in.getNamespaceContext()); + parent.setMetadata(result.getValue()); + } + private void read(final XMLStreamReader in, final AbstractNodeDataWithSchema parent, final String rootElement) throws XMLStreamException { if (!in.hasNext()) { @@ -381,6 +430,20 @@ public final class XmlParserStream implements Closeable, Flushable { return; } + if (parent instanceof OpaqueAnydataNodeDataWithSchema) { + parent.setAttributes(getElementAttributes(in)); + readOpaqueAnydataValue(in, (OpaqueAnydataNodeDataWithSchema) parent); + if (isNextEndDocument(in)) { + return; + } + + if (!isAtElement(in)) { + in.nextTag(); + } + + return; + } + if (parent instanceof YangModeledAnyXmlSchemaNode) { parent.setAttributes(getElementAttributes(in)); } @@ -511,15 +574,17 @@ public final class XmlParserStream implements Closeable, Flushable { private Object translateValueByType(final Object value, final DataSchemaNode node, final NamespaceContext namespaceCtx) { if (node instanceof AnyXmlSchemaNode) { - checkArgument(value instanceof Document); /* - * FIXME: Figure out some YANG extension dispatch, which will - * reuse JSON parsing or XML parsing - anyxml is not well-defined in - * JSON. + * FIXME: Figure out some YANG extension dispatch, which will reuse JSON parsing or XML parsing - + * anyxml is not well-defined in JSON. */ return new DOMSource(((Document) value).getDocumentElement()); } + if (node instanceof AnyDataSchemaNode) { + checkArgument(value instanceof OpaqueData, "Unexpected anydata value %s", value); + return value; + } checkArgument(node instanceof TypedDataSchemaNode); checkArgument(value instanceof String); @@ -559,4 +624,28 @@ public final class XmlParserStream implements Closeable, Flushable { private QNameModule rawXmlNamespace(final String xmlNamespace) { return rawNamespaces.computeIfAbsent(xmlNamespace, nsUri -> QNameModule.create(URI.create(nsUri))); } + + // Utility writer just to keep the event model intact. This requires us to build an intermediate representation, + // simply because we are required to perform list coalescence. + // + // FIXME: 5.0.0: we should be able to negotiate downstream's tolerance to reentrant lists, so that we are not + // not required to keep all the book keeping. Immutable builder should not care as long as structure + // is kept... + private static final class DefaultOpaqueAnydataStreamWriter extends AbstractImmutableOpaqueAnydataStreamWriter { + private Entry<@NonNull OpaqueData, @Nullable ImmutableNormalizedMetadata> result; + + DefaultOpaqueAnydataStreamWriter() { + super(false); + } + + Entry<@NonNull OpaqueData, @Nullable ImmutableNormalizedMetadata> result() { + return result; + } + + @Override + protected void finishAnydata(final OpaqueData opaqueData, final ImmutableNormalizedMetadata metadata) { + checkState(result == null, "Result already set to %s", result); + result = new SimpleImmutableEntry<>(requireNonNull(opaqueData), metadata); + } + } } diff --git a/yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/AnydataParseTest.java b/yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/AnydataParseTest.java new file mode 100644 index 0000000000..87f6c092e4 --- /dev/null +++ b/yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/AnydataParseTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019 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.yangtools.yang.data.codec.xml; + +import static org.junit.Assert.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import org.junit.Test; +import org.opendaylight.yangtools.util.xml.UntrustedXML; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.data.impl.schema.Builders; +import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult; +import org.opendaylight.yangtools.yang.data.util.schema.opaque.OpaqueDataBuilder; +import org.opendaylight.yangtools.yang.data.util.schema.opaque.OpaqueDataContainerBuilder; +import org.xml.sax.SAXException; + +public class AnydataParseTest extends AbstractAnydataTest { + + @Test + public void testOpaqueAnydata() throws XMLStreamException, IOException, URISyntaxException, SAXException { + final XMLStreamReader reader = UntrustedXML.createXMLStreamReader( + toInputStream("")); + + final NormalizedNodeResult result = new NormalizedNodeResult(); + final NormalizedNodeStreamWriter streamWriter = ImmutableNormalizedNodeStreamWriter.from(result); + final XmlParserStream xmlParser = XmlParserStream.create(streamWriter, SCHEMA_CONTEXT, + SCHEMA_CONTEXT.findDataChildByName(FOO_QNAME).get(), true); + xmlParser.parse(reader); + + final NormalizedNode parsed = result.getResult(); + assertEquals(Builders.opaqueAnydataBuilder().withNodeIdentifier(FOO_NODEID) + .withValue(new OpaqueDataBuilder().withAccurateLists(false) + .withRoot(new OpaqueDataContainerBuilder().withIdentifier(BAR_NODEID).build()).build()) + .build(), parsed); + } + + private static InputStream toInputStream(final String str) { + return new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8)); + } +} diff --git a/yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/AbstractAnydataNodeDataWithSchema.java b/yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/AbstractAnydataNodeDataWithSchema.java new file mode 100644 index 0000000000..7e8d6952c0 --- /dev/null +++ b/yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/AbstractAnydataNodeDataWithSchema.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019 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.yangtools.yang.data.util; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.annotations.Beta; +import org.opendaylight.yangtools.rfc7952.data.api.NormalizedMetadata; +import org.opendaylight.yangtools.yang.model.api.AnyDataSchemaNode; + +@Beta +public abstract class AbstractAnydataNodeDataWithSchema extends SimpleNodeDataWithSchema { + private NormalizedMetadata metadata; + + protected AbstractAnydataNodeDataWithSchema(final AnyDataSchemaNode dataSchemaNode) { + super(dataSchemaNode); + } + + @Override + public T getValue() { + return objectModelClass().cast(super.getValue()); + } + + @Override + public void setValue(final Object value) { + final Class clazz = objectModelClass(); + checkArgument(clazz.isInstance(value), "Value %s is not compatible with %s", clazz); + super.setValue(value); + } + + public final NormalizedMetadata getMetadata() { + return metadata; + } + + public final void setMetadata(final NormalizedMetadata value) { + checkState(metadata == null, "Metadata already set to %s", metadata); + metadata = value; + } + + protected abstract Class objectModelClass(); +} diff --git a/yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/OpaqueAnydataNodeDataWithSchema.java b/yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/OpaqueAnydataNodeDataWithSchema.java new file mode 100644 index 0000000000..e5853c8421 --- /dev/null +++ b/yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/OpaqueAnydataNodeDataWithSchema.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2019 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.yangtools.yang.data.util; + +import com.google.common.annotations.Beta; +import com.google.common.collect.ImmutableMap; +import java.io.IOException; +import java.util.List; +import org.opendaylight.yangtools.rfc7952.data.api.NormalizedMetadata; +import org.opendaylight.yangtools.rfc7952.data.api.NormalizedMetadataStreamWriter; +import org.opendaylight.yangtools.rfc7952.data.api.OpaqueAnydataStreamWriter; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.opaque.OpaqueData; +import org.opendaylight.yangtools.yang.data.api.schema.opaque.OpaqueDataContainer; +import org.opendaylight.yangtools.yang.data.api.schema.opaque.OpaqueDataList; +import org.opendaylight.yangtools.yang.data.api.schema.opaque.OpaqueDataNode; +import org.opendaylight.yangtools.yang.data.api.schema.opaque.OpaqueDataValue; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.data.api.schema.stream.OpaqueAnydataExtension; +import org.opendaylight.yangtools.yang.data.api.schema.stream.OpaqueAnydataExtension.StreamWriter; +import org.opendaylight.yangtools.yang.model.api.AnyDataSchemaNode; + +@Beta +public final class OpaqueAnydataNodeDataWithSchema extends AbstractAnydataNodeDataWithSchema { + public OpaqueAnydataNodeDataWithSchema(final AnyDataSchemaNode dataSchemaNode) { + super(dataSchemaNode); + } + + @Override + protected Class objectModelClass() { + return OpaqueData.class; + } + + @Override + protected void write(final NormalizedNodeStreamWriter writer, final NormalizedMetadataStreamWriter metaWriter) + throws IOException { + final OpaqueAnydataExtension ext = writer.getExtensions().getInstance(OpaqueAnydataExtension.class); + if (ext != null) { + writer.nextDataSchemaNode(getSchema()); + streamOpaqueAnydataNode(ext, provideNodeIdentifier(), getValue(), getMetadata()); + writer.endNode(); + } + } + + private static void streamOpaqueAnydataNode(final OpaqueAnydataExtension ext, final NodeIdentifier name, + final OpaqueData data, final NormalizedMetadata metadata) throws IOException { + if (metadata != null) { + final StreamWriter dataWriter = ext.startOpaqueAnydataNode(name, data.hasAccurateLists()); + final OpaqueDataNode node = data.getRoot(); + if (dataWriter instanceof OpaqueAnydataStreamWriter) { + streamOpaqueDataNode((OpaqueAnydataStreamWriter) dataWriter, node, metadata); + } else { + dataWriter.streamOpaqueDataNode(node); + } + } else { + ext.streamOpaqueAnydataNode(name, data); + } + } + + private static void streamOpaqueDataNode(final OpaqueAnydataStreamWriter writer, final OpaqueDataNode node, + final NormalizedMetadata metadata) throws IOException { + if (node instanceof OpaqueDataValue) { + writer.startOpaqueContainer(node.getIdentifier(), 0); + writeMetadata(writer, metadata); + writer.opaqueValue(((OpaqueDataValue) node).getValue()); + writer.endOpaqueNode(); + return; + } + final List children; + if (node instanceof OpaqueDataList) { + children = ((OpaqueDataList) node).getChildren(); + writer.startOpaqueList(node.getIdentifier(), children.size()); + } else if (node instanceof OpaqueDataContainer) { + children = ((OpaqueDataContainer) node).getChildren(); + writer.startOpaqueContainer(node.getIdentifier(), children.size()); + } else { + throw new IllegalStateException("Unhandled node " + node); + } + + writeMetadata(writer, metadata); + for (OpaqueDataNode child : children) { + streamOpaqueDataNode(writer, child, metadata.getChildren().get(child.getIdentifier())); + } + writer.endOpaqueNode(); + } + + private static void writeMetadata(final OpaqueAnydataStreamWriter writer, final NormalizedMetadata metadata) + throws IOException { + if (metadata != null) { + writer.metadata(ImmutableMap.copyOf(metadata.getAnnotations())); + } + } +} -- 2.36.6