Fix RFC8040 root access 94/99494/6
authorTomas Cere <tomas.cere@pantheon.tech>
Thu, 27 Jan 2022 09:55:02 +0000 (10:55 +0100)
committerRobert Varga <nite@hq.sk>
Fri, 28 Jan 2022 11:21:42 +0000 (11:21 +0000)
Requests targetting root need to be wrapped in <data> container
to conform to the RFC, otherwise we end up with multiple roots
when serializing requests targetting root(either rests/data or
root of mounted device).

JIRA: NETCONF-853
Change-Id: I6d2a8d7b235a11779da134ed9c9abec8e8afe27c
Signed-off-by: Tomas Cere <tomas.cere@pantheon.tech>
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
restconf/restconf-nb-rfc8040/src/main/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/XmlNormalizedNodeBodyWriter.java
restconf/restconf-nb-rfc8040/src/test/java/org/opendaylight/restconf/nb/rfc8040/jersey/providers/XmlNormalizedNodeBodyWriterTest.java

index d9af3d4c80a74295f91b5f2518875c3af8c16c83..81a1c17b26bce444568d25e7bb7aee6fe8a4727d 100644 (file)
@@ -35,7 +35,6 @@ import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
 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.codec.xml.XMLStreamNormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
 import org.opendaylight.yangtools.yang.model.api.ActionDefinition;
@@ -129,25 +128,35 @@ public class XmlNormalizedNodeBodyWriter extends AbstractNormalizedNodeBodyWrite
                 nnWriter.write(ImmutableNodes.mapNodeBuilder(data.getIdentifier().getNodeType())
                     .addChild((MapEntryNode) data)
                     .build());
-            } else {
-                if (isRoot && data instanceof ContainerNode && ((ContainerNode) data).isEmpty()) {
+            } else if (isRoot) {
+                if (data instanceof ContainerNode && ((ContainerNode) data).isEmpty()) {
                     writeEmptyDataNode(xmlWriter, data);
                 } else {
-                    nnWriter.write(data);
+                    writeAndWrapInDataNode(xmlWriter, nnWriter, data);
                 }
+            } else {
+                nnWriter.write(data);
             }
         }
 
         nnWriter.flush();
     }
 
-    private static void writeEmptyDataNode(final XMLStreamWriter xmlWriter, final NormalizedNode data)
-            throws IOException {
+    private static RestconfNormalizedNodeWriter createNormalizedNodeWriter(final XMLStreamWriter xmlWriter,
+            final EffectiveModelContext schemaContext, final SchemaPath schemaPath, final DepthParam depth,
+            final List<Set<QName>> fields) {
+        return ParameterAwareNormalizedNodeWriter.forStreamWriter(
+            XMLStreamNormalizedNodeStreamWriter.create(xmlWriter, schemaContext, schemaPath), depth, fields);
+    }
+
+    private static void writeAndWrapInDataNode(final XMLStreamWriter xmlWriter,
+            final RestconfNormalizedNodeWriter nnWriter, final NormalizedNode data) throws IOException {
         final QName nodeType = data.getIdentifier().getNodeType();
         final String namespace = nodeType.getNamespace().toString();
         try {
             xmlWriter.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, nodeType.getLocalName(), namespace);
             xmlWriter.writeDefaultNamespace(namespace);
+            nnWriter.write(data);
             xmlWriter.writeEndElement();
             xmlWriter.flush();
         } catch (XMLStreamException e) {
@@ -155,21 +164,27 @@ public class XmlNormalizedNodeBodyWriter extends AbstractNormalizedNodeBodyWrite
         }
     }
 
-    private static RestconfNormalizedNodeWriter createNormalizedNodeWriter(final XMLStreamWriter xmlWriter,
-            final EffectiveModelContext schemaContext, final SchemaPath schemaPath, final DepthParam depth,
-            final List<Set<QName>> fields) {
-        final NormalizedNodeStreamWriter xmlStreamWriter = XMLStreamNormalizedNodeStreamWriter
-                .create(xmlWriter, schemaContext, schemaPath);
-        return ParameterAwareNormalizedNodeWriter.forStreamWriter(xmlStreamWriter, depth, fields);
+    private static void writeEmptyDataNode(final XMLStreamWriter xmlWriter, final NormalizedNode data)
+            throws IOException {
+        final QName nodeType = data.getIdentifier().getNodeType();
+        final String namespace = nodeType.getNamespace().toString();
+        try {
+            xmlWriter.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, nodeType.getLocalName(), namespace);
+            xmlWriter.writeDefaultNamespace(namespace);
+            xmlWriter.writeEndElement();
+            xmlWriter.flush();
+        } catch (XMLStreamException e) {
+            throw new IOException("Failed to write elements", e);
+        }
     }
 
     private static void writeElements(final XMLStreamWriter xmlWriter, final RestconfNormalizedNodeWriter nnWriter,
             final ContainerNode data) throws IOException {
-        final QName name = data.getIdentifier().getNodeType();
+        final QName nodeType = data.getIdentifier().getNodeType();
+        final String namespace = nodeType.getNamespace().toString();
         try {
-            xmlWriter.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX,
-                    name.getLocalName(), name.getNamespace().toString());
-            xmlWriter.writeDefaultNamespace(name.getNamespace().toString());
+            xmlWriter.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, nodeType.getLocalName(), namespace);
+            xmlWriter.writeDefaultNamespace(namespace);
             for (final NormalizedNode child : data.body()) {
                 nnWriter.write(child);
             }
index f45ce5df9dd9bf97d2cfe47cbe143cfd894c42e9..7d0b37d4d27bc00405d34c08bae58164f994028e 100644 (file)
@@ -8,50 +8,74 @@
 package org.opendaylight.restconf.nb.rfc8040.jersey.providers;
 
 import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 
 import java.io.ByteArrayOutputStream;
-import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
 import javax.ws.rs.core.MediaType;
-import org.junit.BeforeClass;
 import org.junit.Test;
-import org.mockito.Mockito;
 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
+import org.opendaylight.restconf.nb.rfc8040.TestRestconfUtils;
 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
+import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
-import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
 
 public class XmlNormalizedNodeBodyWriterTest {
+    @Test
+    public void testWriteEmptyRootContainer() throws Exception {
+        final EffectiveModelContext schemaContext = mock(EffectiveModelContext.class);
 
-    private static final String EMPTY_OUTPUT = "<data xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"></data>";
+        final SchemaNode schemaNode = mock(SchemaNode.class);
+        doReturn(SchemaPath.ROOT).when(schemaNode).getPath();
 
-    private static EffectiveModelContext schemaContext;
+        final NormalizedNodePayload nodePayload = NormalizedNodePayload.of(
+            new InstanceIdentifierContext<>(YangInstanceIdentifier.empty(), schemaNode, null, schemaContext),
+            Builders.containerBuilder().withNodeIdentifier(new NodeIdentifier(SchemaContext.NAME)).build());
 
-    @BeforeClass
-    public static void initialization() throws Exception {
-        schemaContext = Mockito.mock(EffectiveModelContext.class);
+        final ByteArrayOutputStream output = new ByteArrayOutputStream();
+        final XmlNormalizedNodeBodyWriter xmlWriter = new XmlNormalizedNodeBodyWriter();
+        xmlWriter.writeTo(nodePayload, null, null, null, MediaType.APPLICATION_XML_TYPE, null, output);
+
+        assertEquals("<data xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\"></data>",
+            output.toString(StandardCharsets.UTF_8));
     }
 
     @Test
-    public void testWriteEmptyRootContainer() throws Exception {
-        final SchemaNode schemaNode = Mockito.mock(SchemaNode.class);
-        when(schemaNode.getPath()).thenReturn(SchemaPath.ROOT);
+    public void testRootContainerWrite() throws Exception {
+        final EffectiveModelContext schemaContext =
+                TestRestconfUtils.loadSchemaContext("/instanceidentifier/yang", null);
+
+        final SchemaNode schemaNode = mock(SchemaNode.class);
+        doReturn(SchemaPath.ROOT).when(schemaNode).getPath();
 
-        final InstanceIdentifierContext<SchemaNode> identifierContext =
-                new InstanceIdentifierContext<>(YangInstanceIdentifier.empty(), schemaNode, null, schemaContext);
-        final ContainerNode data = ImmutableContainerNodeBuilder.create()
-                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(SchemaContext.NAME)).build();
-        final NormalizedNodePayload nodePayload = NormalizedNodePayload.of(identifierContext, data);
+        final NormalizedNodePayload nodePayload = NormalizedNodePayload.of(
+            new InstanceIdentifierContext<>(YangInstanceIdentifier.empty(), schemaNode, null, schemaContext),
+            Builders.containerBuilder()
+                .withNodeIdentifier(new NodeIdentifier(SchemaContext.NAME))
+                .withChild(Builders.containerBuilder()
+                    .withNodeIdentifier(new NodeIdentifier(
+                        QName.create("foo:module", "2016-09-29", "foo-bar-container")))
+                    .build())
+                .withChild(Builders.containerBuilder()
+                    .withNodeIdentifier(new NodeIdentifier(
+                        QName.create("bar:module", "2016-09-29", "foo-bar-container")))
+                    .build())
+                .build());
 
-        final OutputStream output = new ByteArrayOutputStream();
+        final ByteArrayOutputStream output = new ByteArrayOutputStream();
         final XmlNormalizedNodeBodyWriter xmlWriter = new XmlNormalizedNodeBodyWriter();
         xmlWriter.writeTo(nodePayload, null, null, null, MediaType.APPLICATION_XML_TYPE, null, output);
 
-        assertEquals(EMPTY_OUTPUT, output.toString());
+        assertEquals("<data xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">"
+            + "<foo-bar-container xmlns=\"bar:module\"></foo-bar-container>"
+            + "<foo-bar-container xmlns=\"foo:module\"></foo-bar-container>"
+            + "</data>", output.toString(StandardCharsets.UTF_8));
     }
 }
\ No newline at end of file