Bug 8745: Add support for parsing attributes to the new XML parser 18/60018/9
authorIgor Foltin <igor.foltin@pantheon.tech>
Thu, 6 Jul 2017 11:53:17 +0000 (13:53 +0200)
committerIgor Foltin <igor.foltin@pantheon.tech>
Tue, 18 Jul 2017 14:44:17 +0000 (16:44 +0200)
New XML parser is now able to parse XML attributes in order to provide
necessary information for processing edit-config messages in NETCONF.

Make some members of ImmutableNormalizedNodeStreamWriter
protected so they can be reused
in the EditOperationNormalizedNodeStreamWriter extension in NETCONF.

Add a unit test

This patch is a prerequisite for the following change in NETCONF:
https://git.opendaylight.org/gerrit/#/c/60014/

Change-Id: Idfd87279814e324dc914809fb09abfbb27eefa4a
Signed-off-by: Igor Foltin <igor.foltin@pantheon.tech>
yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/XmlParserStream.java
yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/Bug8745Test.java [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/bug8745/foo.xml [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/bug8745/foo.yang [new file with mode: 0644]
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/ImmutableNormalizedNodeStreamWriter.java
yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/AbstractNodeDataWithSchema.java
yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/ContainerNodeDataWithSchema.java
yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/LeafListEntryNodeDataWithSchema.java
yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/LeafNodeDataWithSchema.java
yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/ListEntryNodeDataWithSchema.java
yang/yang-data-util/src/main/java/org/opendaylight/yangtools/yang/data/util/YangModeledAnyXmlNodeDataWithSchema.java

index 7293b70e4c32c5c48de388d5a4ae39557db982bf..59afd1002a96fe29b00a60e008af54bf2a64199b 100644 (file)
@@ -10,6 +10,7 @@ package org.opendaylight.yangtools.yang.data.codec.xml;
 
 import com.google.common.annotations.Beta;
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
 import java.io.Closeable;
 import java.io.Flushable;
 import java.io.IOException;
@@ -18,8 +19,11 @@ import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.Deque;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
 import java.util.Set;
 import javax.annotation.concurrent.NotThreadSafe;
+import javax.xml.XMLConstants;
 import javax.xml.namespace.NamespaceContext;
 import javax.xml.parsers.ParserConfigurationException;
 import javax.xml.stream.Location;
@@ -28,6 +32,7 @@ import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamReader;
 import javax.xml.transform.dom.DOMSource;
 import org.opendaylight.yangtools.util.xml.UntrustedXML;
+import org.opendaylight.yangtools.yang.common.QName;
 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;
@@ -189,6 +194,29 @@ public final class XmlParserStream implements Closeable, Flushable {
         return this;
     }
 
+    private static Map<QName, String> getElementAttributes(final XMLStreamReader in) {
+        Preconditions.checkState(in.isStartElement(), "Attributes can be extracted only from START_ELEMENT.");
+        final Map<QName, String> attributes = new LinkedHashMap<>();
+
+        for (int attrIndex = 0; attrIndex < in.getAttributeCount(); attrIndex++) {
+            String attributeNS = in.getAttributeNamespace(attrIndex);
+
+            if (attributeNS == null) {
+                attributeNS = "";
+            }
+
+            // Skip namespace definitions
+            if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(attributeNS)) {
+                continue;
+            }
+
+            final QName qName = new QName(URI.create(attributeNS), in.getAttributeLocalName(attrIndex));
+            attributes.put(qName, in.getAttributeValue(attrIndex));
+        }
+
+        return ImmutableMap.copyOf(attributes);
+    }
+
     private static String readAnyXmlValue(final XMLStreamReader in) throws XMLStreamException {
         final StringBuilder sb = new StringBuilder();
         final String anyXmlElementName = in.getLocalName();
@@ -221,6 +249,7 @@ public final class XmlParserStream implements Closeable, Flushable {
         }
 
         if (parent instanceof LeafNodeDataWithSchema || parent instanceof LeafListEntryNodeDataWithSchema) {
+            parent.setAttributes(getElementAttributes(in));
             setValue(parent, in.getElementText().trim(), in.getNamespaceContext());
             if (isNextEndDocument(in)) {
                 return;
@@ -232,6 +261,10 @@ public final class XmlParserStream implements Closeable, Flushable {
             return;
         }
 
+        if (parent instanceof ListEntryNodeDataWithSchema || parent instanceof ContainerNodeDataWithSchema) {
+            parent.setAttributes(getElementAttributes(in));
+        }
+
         if (parent instanceof LeafListNodeDataWithSchema || parent instanceof ListNodeDataWithSchema) {
             String xmlElementName = in.getLocalName();
             while (xmlElementName.equals(parent.getSchema().getQName().getLocalName())) {
@@ -258,6 +291,10 @@ public final class XmlParserStream implements Closeable, Flushable {
             return;
         }
 
+        if (parent instanceof YangModeledAnyXmlSchemaNode) {
+            parent.setAttributes(getElementAttributes(in));
+        }
+
         switch (in.nextTag()) {
             case XMLStreamConstants.START_ELEMENT:
                 final Set<String> namesakes = new HashSet<>();
diff --git a/yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/Bug8745Test.java b/yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/Bug8745Test.java
new file mode 100644 (file)
index 0000000..dd8fc35
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2017 Pantheon Technologies 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 com.google.common.base.Preconditions;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.stream.XMLStreamWriter;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import javax.xml.transform.dom.DOMResult;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import org.custommonkey.xmlunit.Diff;
+import org.custommonkey.xmlunit.XMLAssert;
+import org.custommonkey.xmlunit.XMLUnit;
+import org.junit.Test;
+import org.opendaylight.yangtools.util.xml.UntrustedXML;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
+import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.xml.sax.SAXException;
+
+public class Bug8745Test {
+
+    @Test
+    public void testParsingAttributes() throws Exception {
+        final SchemaContext schemaContext = YangParserTestUtils.parseYangSource("/bug8745/foo.yang");
+        final QName contWithAttributes = QName.create("foo", "1970-01-01", "cont-with-attributes");
+        final ContainerSchemaNode contWithAttr = (ContainerSchemaNode) SchemaContextUtil.findDataSchemaNode(
+                schemaContext, SchemaPath.create(true, contWithAttributes));
+
+        final Document doc = loadDocument("/bug8745/foo.xml");
+
+        final DOMResult domResult = new DOMResult(UntrustedXML.newDocumentBuilder().newDocument());
+
+        final XMLOutputFactory outputfactory = XMLOutputFactory.newInstance();
+        outputfactory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, Boolean.TRUE);
+
+        final XMLStreamWriter xmlStreamWriter = outputfactory.createXMLStreamWriter(domResult);
+
+        final NormalizedNodeStreamWriter streamWriter = XMLStreamNormalizedNodeStreamWriter.create(
+                xmlStreamWriter, schemaContext);
+
+        final InputStream resourceAsStream = Bug8745Test.class.getResourceAsStream(
+                "/bug8745/foo.xml");
+        final XMLInputFactory inputFactory = XMLInputFactory.newInstance();
+        final XMLStreamReader reader = inputFactory.createXMLStreamReader(resourceAsStream);
+
+        final XmlParserStream xmlParser = XmlParserStream.create(streamWriter, schemaContext, contWithAttr);
+        xmlParser.parse(reader);
+
+        XMLUnit.setIgnoreWhitespace(true);
+        XMLUnit.setNormalize(true);
+
+        final String expectedXml = toString(doc.getDocumentElement());
+        final String serializedXml = toString(domResult.getNode());
+        final Diff diff = new Diff(expectedXml, serializedXml);
+
+        XMLAssert.assertXMLEqual(diff, true);
+    }
+
+    private static Document loadDocument(final String xmlPath) throws IOException, SAXException {
+        final InputStream resourceAsStream = NormalizedNodesToXmlTest.class.getResourceAsStream(xmlPath);
+        final Document currentConfigElement = readXmlToDocument(resourceAsStream);
+        Preconditions.checkNotNull(currentConfigElement);
+        return currentConfigElement;
+    }
+
+    private static Document readXmlToDocument(final InputStream xmlContent) throws IOException, SAXException {
+        final Document doc = UntrustedXML.newDocumentBuilder().parse(xmlContent);
+        doc.getDocumentElement().normalize();
+        return doc;
+    }
+
+    private static String toString(final Node xml) {
+        try {
+            final Transformer transformer = TransformerFactory.newInstance().newTransformer();
+            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
+
+            final StreamResult result = new StreamResult(new StringWriter());
+            final DOMSource source = new DOMSource(xml);
+            transformer.transform(source, result);
+
+            return result.getWriter().toString();
+        } catch (IllegalArgumentException | TransformerFactoryConfigurationError | TransformerException e) {
+            throw new RuntimeException("Unable to serialize xml element " + xml, e);
+        }
+    }
+}
diff --git a/yang/yang-data-codec-xml/src/test/resources/bug8745/foo.xml b/yang/yang-data-codec-xml/src/test/resources/bug8745/foo.xml
new file mode 100644 (file)
index 0000000..304b11b
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<cont-with-attributes xmlns="foo" xmlns:a="attribute-ns" a:operation="delete">
+    <leaf-with-attributes xmlns:a="attribute-ns" a:operation="create">lf-val</leaf-with-attributes>
+    <leaf-list-with-attributes xmlns:a="attribute-ns" a:operation="replace">lfl-val</leaf-list-with-attributes>
+    <list-with-attributes xmlns:a="attribute-ns" a:operation="merge">
+        <list-key>key-val</list-key>
+    </list-with-attributes>
+</cont-with-attributes>
\ No newline at end of file
diff --git a/yang/yang-data-codec-xml/src/test/resources/bug8745/foo.yang b/yang/yang-data-codec-xml/src/test/resources/bug8745/foo.yang
new file mode 100644 (file)
index 0000000..c2958d5
--- /dev/null
@@ -0,0 +1,22 @@
+module foo {
+    namespace foo;
+    prefix foo;
+
+    container cont-with-attributes {
+        leaf leaf-with-attributes {
+            type string;
+        }
+
+        leaf-list leaf-list-with-attributes {
+            type string;
+        }
+
+        list list-with-attributes {
+            key list-key;
+
+            leaf list-key {
+                type string;
+            }
+        }
+    }
+}
\ No newline at end of file
index 2b6c3dc3b2806dfc9a5be6ac4193fbd06c3418b6..e20a481296759ec9b5c375ce38f703b17d3d1313 100644 (file)
@@ -119,8 +119,12 @@ public class ImmutableNormalizedNodeStreamWriter implements NormalizedNodeStream
         return new ImmutableNormalizedNodeStreamWriter(result);
     }
 
+    protected Deque<NormalizedNodeContainerBuilder> getBuilders() {
+        return builders;
+    }
+
     @SuppressWarnings("rawtypes")
-    private NormalizedNodeContainerBuilder getCurrent() {
+    protected NormalizedNodeContainerBuilder getCurrent() {
         return builders.peek();
     }
 
@@ -303,7 +307,7 @@ public class ImmutableNormalizedNodeStreamWriter implements NormalizedNodeStream
     }
 
     @SuppressWarnings("rawtypes")
-    private static final class NormalizedNodeResultBuilder implements NormalizedNodeContainerBuilder {
+    protected static final class NormalizedNodeResultBuilder implements NormalizedNodeContainerBuilder {
 
         private final NormalizedNodeResult result;
 
index b6368c768e4c057e143d9469926f0ef67a8bd776..db7cb770650ad1f438d0387592124a0d2e5eac0a 100644 (file)
@@ -10,7 +10,9 @@ package org.opendaylight.yangtools.yang.data.util;
 import com.google.common.annotations.Beta;
 import com.google.common.base.Preconditions;
 import java.io.IOException;
+import java.util.Map;
 import java.util.Objects;
+import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
@@ -22,6 +24,7 @@ import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 @Beta
 public abstract class AbstractNodeDataWithSchema {
     private final DataSchemaNode schema;
+    private Map<QName, String> attributes;
 
     public AbstractNodeDataWithSchema(final DataSchemaNode schema) {
         this.schema = Preconditions.checkNotNull(schema);
@@ -36,6 +39,26 @@ public abstract class AbstractNodeDataWithSchema {
         return schema;
     }
 
+    /**
+     * Set the associated attributes.
+     *
+     * @param attributes parsed attributes
+     */
+    public final void setAttributes(final Map<QName, String> attributes) {
+        Preconditions.checkState(this.attributes == null, "Node '%s' has already set its attributes to %s.",
+                getSchema().getQName(), this.attributes);
+        this.attributes = attributes;
+    }
+
+    /**
+     * Return the associated attributes.
+     *
+     * @return associated attributes
+     */
+    public final Map<QName, String> getAttributes() {
+        return attributes;
+    }
+
     /**
      * Emit this node's events into the specified writer.
      *
index 3ebc481d2119079d86e549c033d9fca425f7a620..623199c090b9ddc9f3c22dbff84cf09e5610bd41 100644 (file)
@@ -8,6 +8,7 @@
 package org.opendaylight.yangtools.yang.data.util;
 
 import java.io.IOException;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamAttributeWriter;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 
@@ -27,7 +28,14 @@ public class ContainerNodeDataWithSchema extends CompositeNodeDataWithSchema {
     @Override
     public void write(final NormalizedNodeStreamWriter writer) throws IOException {
         writer.nextDataSchemaNode(getSchema());
-        writer.startContainerNode(provideNodeIdentifier(), childSizeHint());
+
+        if (writer instanceof NormalizedNodeStreamAttributeWriter && getAttributes() != null) {
+            ((NormalizedNodeStreamAttributeWriter) writer).startContainerNode(provideNodeIdentifier(), childSizeHint(),
+                    getAttributes());
+        } else {
+            writer.startContainerNode(provideNodeIdentifier(), childSizeHint());
+        }
+
         super.write(writer);
         writer.endNode();
     }
index ff65f3ca03762000f73607ad656a1c6575be7027..8373dc7e528caeae52455c150936e314fc1e6aaa 100644 (file)
@@ -8,6 +8,7 @@
 package org.opendaylight.yangtools.yang.data.util;
 
 import java.io.IOException;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamAttributeWriter;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 
@@ -26,6 +27,12 @@ public class LeafListEntryNodeDataWithSchema extends SimpleNodeDataWithSchema {
     @Override
     public void write(final NormalizedNodeStreamWriter writer) throws IOException {
         writer.nextDataSchemaNode(getSchema());
-        writer.leafSetEntryNode(getSchema().getQName(), getValue());
+
+        if (writer instanceof NormalizedNodeStreamAttributeWriter && getAttributes() != null) {
+            ((NormalizedNodeStreamAttributeWriter) writer).leafSetEntryNode(getSchema().getQName(), getValue(),
+                    getAttributes());
+        } else {
+            writer.leafSetEntryNode(getSchema().getQName(), getValue());
+        }
     }
 }
index c3e6bf81eda7a20a50c08529b8ddc98386df3afd..380ca99c32b39e053d6b78c2c5ac5016d481aa67 100644 (file)
@@ -8,6 +8,7 @@
 package org.opendaylight.yangtools.yang.data.util;
 
 import java.io.IOException;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamAttributeWriter;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 
@@ -27,6 +28,12 @@ public class LeafNodeDataWithSchema extends SimpleNodeDataWithSchema {
     @Override
     public void write(final NormalizedNodeStreamWriter writer) throws IOException {
         writer.nextDataSchemaNode(getSchema());
-        writer.leafNode(provideNodeIdentifier(), getValue());
+
+        if (writer instanceof NormalizedNodeStreamAttributeWriter && getAttributes() != null) {
+            ((NormalizedNodeStreamAttributeWriter) writer).leafNode(provideNodeIdentifier(), getValue(),
+                    getAttributes());
+        } else {
+            writer.leafNode(provideNodeIdentifier(), getValue());
+        }
     }
 }
index 13a734d28d1b4fdae48cfaf1a9a999aae69db296..8c751339105ad07733bc4d9bf6ee95561cfdbb72 100644 (file)
@@ -15,6 +15,7 @@ import java.util.LinkedHashMap;
 import java.util.Map;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamAttributeWriter;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
@@ -74,9 +75,16 @@ public class ListEntryNodeDataWithSchema extends CompositeNodeDataWithSchema {
         }
 
         writer.nextDataSchemaNode(getSchema());
-        writer.startMapEntryNode(
-            new NodeIdentifierWithPredicates(getSchema().getQName(), predicates),
-            childSizeHint());
+
+        if (writer instanceof NormalizedNodeStreamAttributeWriter && getAttributes() != null) {
+            ((NormalizedNodeStreamAttributeWriter) writer).startMapEntryNode(
+                    new NodeIdentifierWithPredicates(getSchema().getQName(), predicates), childSizeHint(),
+                    getAttributes());
+        } else {
+            writer.startMapEntryNode(new NodeIdentifierWithPredicates(getSchema().getQName(), predicates),
+                    childSizeHint());
+        }
+
         super.write(writer);
         writer.endNode();
     }
index 4e50c5701dfaeb692e55e580eb70f76405998b4b..544eb9ca7c55822711f15f632cfff5d02c5457ab 100644 (file)
@@ -8,6 +8,7 @@
 package org.opendaylight.yangtools.yang.data.util;
 
 import java.io.IOException;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamAttributeWriter;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.model.api.YangModeledAnyXmlSchemaNode;
 
@@ -27,7 +28,14 @@ public final class YangModeledAnyXmlNodeDataWithSchema extends CompositeNodeData
     @Override
     public void write(final NormalizedNodeStreamWriter writer) throws IOException {
         writer.nextDataSchemaNode(getSchema());
-        writer.startYangModeledAnyXmlNode(provideNodeIdentifier(), childSizeHint());
+
+        if (writer instanceof NormalizedNodeStreamAttributeWriter && getAttributes() != null) {
+            ((NormalizedNodeStreamAttributeWriter) writer).startYangModeledAnyXmlNode(provideNodeIdentifier(),
+                    childSizeHint(), getAttributes());
+        } else {
+            writer.startYangModeledAnyXmlNode(provideNodeIdentifier(), childSizeHint());
+        }
+
         super.write(writer);
         writer.endNode();
     }