Add support for opaque anydata XML output 57/82157/22
authorRobert Varga <robert.varga@pantheon.tech>
Fri, 17 May 2019 00:07:37 +0000 (02:07 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Tue, 21 May 2019 12:59:35 +0000 (14:59 +0200)
Streaming opaque anydata content requires simple element emission,
which is implemented by this patch along with a very simple unit
test.

JIRA: YANGTOOLS-991
Change-Id: Ia1730b7415a34fcabbb936c7ef06e5a8522a2299
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/SchemaAwareXMLStreamNormalizedNodeStreamWriter.java
yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/SchemalessXMLStreamNormalizedNodeStreamWriter.java
yang/yang-data-codec-xml/src/main/java/org/opendaylight/yangtools/yang/data/codec/xml/XMLStreamNormalizedNodeStreamWriter.java
yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/AbstractAnydataTest.java [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/AnydataSerializeTest.java [new file with mode: 0644]
yang/yang-data-codec-xml/src/test/resources/test-anydata.yang [new file with mode: 0644]
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/codec/SchemaTracker.java

index a4510dc8a3b3cf4c9b132a329eab9d4d859c888a..63d8cb8c00ece87a682de1f87c20aeb6c0c58807 100644 (file)
@@ -24,6 +24,7 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdent
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.impl.codec.SchemaTracker;
+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.LeafListSchemaNode;
@@ -85,7 +86,8 @@ final class SchemaAwareXMLStreamNormalizedNodeStreamWriter extends XMLStreamNorm
             if (parent == schema) {
                 endElement();
             }
-        } else if (schema instanceof ContainerSchemaNode || schema instanceof LeafSchemaNode) {
+        } else if (schema instanceof ContainerSchemaNode || schema instanceof LeafSchemaNode
+                || schema instanceof AnyDataSchemaNode) {
             endElement();
         }
     }
@@ -159,4 +161,9 @@ final class SchemaAwareXMLStreamNormalizedNodeStreamWriter extends XMLStreamNorm
         checkState(current instanceof AnyXmlSchemaNode, "Unexpected scala value %s with %s", value, current);
         anyxmlValue(value);
     }
+
+    @Override
+    SchemaNode startAnydata(final NodeIdentifier name) {
+        return tracker.startAnydataNode(name);
+    }
 }
index cb1c4a7312f57ffe5b7909e4002d644f306eef8d..0e5b4c4be7203e247c931f391ede585e087f6268 100644 (file)
@@ -138,4 +138,10 @@ final class SchemalessXMLStreamNormalizedNodeStreamWriter extends XMLStreamNorma
                 break;
         }
     }
+
+    @Override
+    Object startAnydata(final NodeIdentifier name) {
+        nodeTypeStack.push(NodeType.ANY_XML);
+        return null;
+    }
 }
index 435b3a877833b24964f2e980e8261e4b6d636035..e239845e23a1396684df605754a989b56679f957 100644 (file)
@@ -10,9 +10,10 @@ package org.opendaylight.yangtools.yang.data.codec.xml;
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.collect.ClassToInstanceMap;
-import com.google.common.collect.ImmutableClassToInstanceMap;
 import com.google.common.collect.ImmutableMap;
 import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.Deque;
 import java.util.Map.Entry;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -20,13 +21,16 @@ import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamWriter;
 import javax.xml.transform.dom.DOMSource;
 import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.concepts.ObjectExtensions;
 import org.opendaylight.yangtools.rfc7952.data.api.NormalizedMetadataStreamWriter;
+import org.opendaylight.yangtools.rfc7952.data.api.OpaqueAnydataStreamWriter;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriterExtension;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.OpaqueAnydataExtension;
 import org.opendaylight.yangtools.yang.data.impl.codec.SchemaTracker;
 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
@@ -49,10 +53,16 @@ import org.w3c.dom.Node;
  * removed in a future version.
  */
 public abstract class XMLStreamNormalizedNodeStreamWriter<T> implements NormalizedNodeStreamWriter,
-        NormalizedMetadataStreamWriter {
+        NormalizedMetadataStreamWriter, OpaqueAnydataExtension {
     private static final Logger LOG = LoggerFactory.getLogger(XMLStreamNormalizedNodeStreamWriter.class);
     private static final Set<String> BROKEN_ATTRIBUTES = ConcurrentHashMap.newKeySet();
 
+    @SuppressWarnings("rawtypes")
+    static final ObjectExtensions.Factory<XMLStreamNormalizedNodeStreamWriter, NormalizedNodeStreamWriter,
+        NormalizedNodeStreamWriterExtension> EXTENSIONS_BUILDER = ObjectExtensions.factory(
+            XMLStreamNormalizedNodeStreamWriter.class, NormalizedMetadataStreamWriter.class,
+            OpaqueAnydataExtension.class);
+
     private final @NonNull StreamWriterFacade facade;
 
     XMLStreamNormalizedNodeStreamWriter(final XMLStreamWriter writer) {
@@ -111,9 +121,11 @@ public abstract class XMLStreamNormalizedNodeStreamWriter<T> implements Normaliz
 
     @Override
     public final ClassToInstanceMap<NormalizedNodeStreamWriterExtension> getExtensions() {
-        return ImmutableClassToInstanceMap.of(NormalizedMetadataStreamWriter.class, this);
+        return EXTENSIONS_BUILDER.newInstance(this);
     }
 
+    abstract T startAnydata(NodeIdentifier name);
+
     abstract void startList(NodeIdentifier name);
 
     abstract void startListItem(PathArgument name) throws IOException;
@@ -237,4 +249,58 @@ public abstract class XMLStreamNormalizedNodeStreamWriter<T> implements Normaliz
             }
         }
     }
+
+    @Override
+    public final OpaqueAnydataExtension.StreamWriter startOpaqueAnydataNode(final NodeIdentifier name,
+            final boolean accurateLists) throws IOException {
+        final T schema = startAnydata(name);
+        startElement(name.getNodeType());
+        return new XMLOpaqueStreamWriter(schema);
+    }
+
+    private final class XMLOpaqueStreamWriter implements OpaqueAnydataStreamWriter {
+        private final Deque<Boolean> stack = new ArrayDeque<>();
+        private final T valueContext;
+
+        XMLOpaqueStreamWriter(final T valueContext) {
+            this.valueContext = valueContext;
+        }
+
+        @Override
+        public void startOpaqueList(final NodeIdentifier name, final int childSizeHint) throws IOException {
+            stack.push(Boolean.FALSE);
+        }
+
+        @Override
+        public void startOpaqueContainer(final NodeIdentifier name, final int childSizeHint) throws IOException {
+            stack.push(Boolean.TRUE);
+            startElement(name.getNodeType());
+        }
+
+        @Override
+        public void opaqueValue(final Object value) throws IOException {
+            writeValue(value, valueContext);
+        }
+
+        @Override
+        public void endOpaqueNode() throws IOException {
+            if (stack.pop()) {
+                endElement();
+            }
+        }
+
+        @Override
+        public boolean requireMetadataFirst() {
+            return true;
+        }
+
+        @Override
+        public void metadata(final ImmutableMap<QName, Object> metadata) throws IOException {
+            if (stack.peek()) {
+                XMLStreamNormalizedNodeStreamWriter.this.metadata(metadata);
+            } else {
+                LOG.debug("Ignoring metadata {}", metadata);
+            }
+        }
+    }
 }
diff --git a/yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/AbstractAnydataTest.java b/yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/AbstractAnydataTest.java
new file mode 100644 (file)
index 0000000..58c94a7
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * 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 org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
+
+public abstract class AbstractAnydataTest {
+    static final QName FOO_QNAME = QName.create("test-anydata", "foo");
+    static final QName BAR_QNAME = QName.create(FOO_QNAME, "bar");
+    static final NodeIdentifier FOO_NODEID = NodeIdentifier.create(FOO_QNAME);
+    static final NodeIdentifier BAR_NODEID = NodeIdentifier.create(BAR_QNAME);
+
+    static SchemaContext SCHEMA_CONTEXT;
+
+    @BeforeClass
+    public static void beforeClass() throws Exception {
+        SCHEMA_CONTEXT = YangParserTestUtils.parseYangResource("/test-anydata.yang");
+    }
+
+    @AfterClass
+    public static void afterClass() {
+        SCHEMA_CONTEXT = null;
+    }
+
+
+}
diff --git a/yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/AnydataSerializeTest.java b/yang/yang-data-codec-xml/src/test/java/org/opendaylight/yangtools/yang/data/codec/xml/AnydataSerializeTest.java
new file mode 100644 (file)
index 0000000..f62d88d
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * 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.IOException;
+import java.io.StringWriter;
+import java.util.Collection;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.data.util.schema.opaque.OpaqueDataBuilder;
+import org.opendaylight.yangtools.yang.data.util.schema.opaque.OpaqueDataContainerBuilder;
+import org.opendaylight.yangtools.yang.data.util.schema.opaque.OpaqueDataListBuilder;
+
+@RunWith(Parameterized.class)
+public class AnydataSerializeTest extends AbstractAnydataTest {
+
+    @Parameterized.Parameters(name = "{0}")
+    public static Collection<Object[]> data() {
+        return TestFactories.junitParameters();
+    }
+
+    private final XMLOutputFactory factory;
+
+    public AnydataSerializeTest(final String factoryMode, final XMLOutputFactory factory) {
+        this.factory = factory;
+    }
+
+    @Test
+    public void testOpaqueAnydata() throws XMLStreamException, IOException {
+        final StringWriter writer = new StringWriter();
+        final XMLStreamWriter xmlStreamWriter = factory.createXMLStreamWriter(writer);
+
+        final NormalizedNodeStreamWriter xmlNormalizedNodeStreamWriter = XMLStreamNormalizedNodeStreamWriter.create(
+            xmlStreamWriter, SCHEMA_CONTEXT);
+        final NormalizedNodeWriter normalizedNodeWriter = NormalizedNodeWriter.forStreamWriter(
+            xmlNormalizedNodeStreamWriter);
+        normalizedNodeWriter.write(Builders.opaqueAnydataBuilder().withNodeIdentifier(FOO_NODEID)
+            .withValue(new OpaqueDataBuilder().withAccurateLists(false)
+                .withRoot(
+                    new OpaqueDataListBuilder().withIdentifier(BAR_NODEID)
+                    .withChild(new OpaqueDataContainerBuilder().withIdentifier(BAR_NODEID).build())
+                    .build())
+                .build())
+            .build());
+        normalizedNodeWriter.flush();
+
+        final String serializedXml = writer.toString();
+        assertEquals("<foo xmlns=\"test-anydata\"><bar/></foo>", serializedXml);
+    }
+}
diff --git a/yang/yang-data-codec-xml/src/test/resources/test-anydata.yang b/yang/yang-data-codec-xml/src/test/resources/test-anydata.yang
new file mode 100644 (file)
index 0000000..d141015
--- /dev/null
@@ -0,0 +1,8 @@
+module test-anydata {
+    yang-version 1.1;
+    namespace test-anydata;
+    prefix ta;
+
+    anydata foo;
+}
+
index d0475b64cc81b7c0714cac33828d0ae2c8b1b535..f30c6fb0988ef93fd9b33204ebfd8890f8b4344c 100644 (file)
@@ -26,6 +26,7 @@ import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithV
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.data.impl.schema.SchemaUtils;
+import org.opendaylight.yangtools.yang.model.api.AnyDataSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
@@ -252,6 +253,18 @@ public final class SchemaTracker {
         schemaStack.push(anyxmlNode(name));
     }
 
+    public AnyDataSchemaNode anydataNode(final NodeIdentifier name) {
+        final SchemaNode schema = getSchema(name);
+        checkArgument(schema instanceof AnyDataSchemaNode, "Node %s is not anydata", schema.getPath());
+        return (AnyDataSchemaNode)schema;
+    }
+
+    public AnyDataSchemaNode startAnydataNode(final NodeIdentifier name) {
+        final AnyDataSchemaNode ret = anydataNode(name);
+        schemaStack.push(ret);
+        return ret;
+    }
+
     public Object endNode() {
         return schemaStack.pop();
     }