Do not instantiate NormalizedNodes for filter 13/75513/8
authorRobert Varga <robert.varga@pantheon.tech>
Mon, 27 Aug 2018 10:43:27 +0000 (12:43 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Mon, 27 Aug 2018 19:13:42 +0000 (21:13 +0200)
Using ImmutableNodes.fromInstanceId() does not work for leaf nodes,
as we do not have a value, which violates LeafNode contract -- leading
to an IllegalStateException when a user attempts to read() a single
leaf via a transaction.

As it turns out, this is completely unnecessary, as all we are doing
is iterating through the temporary NormalizedNode and creating
NormalizedNodeStreamWriter events based on that.

This patch replicates ImmutableNodes.fromInstanceId() logic, but
simplifies it for this particular task, short-cutting NormalizedNode
creation and emitting events directly as we are iterating over
the YangInstanceIdentifier.

JIRA: NETCONF-563
Change-Id: I0781e81d812ee979deaafb9dfa811e143866f4af
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
netconf/netconf-util/src/main/java/org/opendaylight/netconf/util/NetconfUtil.java
netconf/netconf-util/src/main/java/org/opendaylight/netconf/util/StreamingContext.java [new file with mode: 0644]
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/util/NetconfMessageTransformUtil.java
netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/schema/mapping/NetconfMessageTransformerTest.java

index 46bb6cbcd323b10401607814b3057eee4af78743..eb3613534a15bd31a411b670ed9a1e063a2ccd83 100644 (file)
@@ -9,6 +9,7 @@ package org.opendaylight.netconf.util;
 
 import com.google.common.base.Preconditions;
 import java.io.IOException;
+import java.util.Iterator;
 import javax.xml.stream.XMLOutputFactory;
 import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamWriter;
@@ -17,6 +18,8 @@ import org.opendaylight.netconf.api.DocumentedException;
 import org.opendaylight.netconf.api.xml.XmlElement;
 import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
 import org.opendaylight.netconf.api.xml.XmlUtil;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 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.api.schema.stream.NormalizedNodeWriter;
@@ -74,4 +77,25 @@ public final class NetconfUtil {
             }
         }
     }
+
+    public static void writeFilter(final YangInstanceIdentifier query, final DOMResult result,
+            final SchemaPath schemaPath, final SchemaContext context) throws IOException, XMLStreamException {
+        if (query.isEmpty()) {
+            // No query at all
+            return;
+        }
+
+        final XMLStreamWriter xmlWriter = XML_FACTORY.createXMLStreamWriter(result);
+        try {
+            try (NormalizedNodeStreamWriter writer =
+                    XMLStreamNormalizedNodeStreamWriter.create(xmlWriter, context, schemaPath)) {
+                final Iterator<PathArgument> it = query.getPathArguments().iterator();
+                final PathArgument first = it.next();
+                StreamingContext.fromSchemaAndQNameChecked(context, first.getNodeType()).streamToWriter(writer, first,
+                    it);
+            }
+        } finally {
+            xmlWriter.close();
+        }
+    }
 }
diff --git a/netconf/netconf-util/src/main/java/org/opendaylight/netconf/util/StreamingContext.java b/netconf/netconf-util/src/main/java/org/opendaylight/netconf/util/StreamingContext.java
new file mode 100644 (file)
index 0000000..e139360
--- /dev/null
@@ -0,0 +1,464 @@
+/*
+ * Copyright (c) 2018 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.netconf.util;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter.UNKNOWN_SIZE;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import org.opendaylight.yangtools.concepts.Identifiable;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
+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.NodeWithValue;
+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.util.DataSchemaContextNode;
+import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
+import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.util.EffectiveAugmentationSchema;
+
+abstract class StreamingContext<T extends PathArgument> implements Identifiable<T> {
+    private final T identifier;
+
+    StreamingContext(final T identifier) {
+        this.identifier = identifier;
+    }
+
+    static StreamingContext<?> fromSchemaAndQNameChecked(final DataNodeContainer schema, final QName child) {
+        final Optional<DataSchemaNode> potential = findChildSchemaNode(schema, child);
+        checkArgument(potential.isPresent(),
+                "Supplied QName %s is not valid according to schema %s, potential children nodes: %s", child, schema,
+                schema.getChildNodes());
+
+        final DataSchemaNode result = potential.get();
+        // We try to look up if this node was added by augmentation
+        if (schema instanceof DataSchemaNode && result.isAugmenting()) {
+            for (final AugmentationSchemaNode aug : ((AugmentationTarget)schema).getAvailableAugmentations()) {
+                final DataSchemaNode found = aug.getDataChildByName(result.getQName());
+                if (found != null) {
+                    return new Augmentation(aug, schema);
+                }
+            }
+        }
+        return fromDataSchemaNode(result);
+    }
+
+    static StreamingContext<?> fromDataSchemaNode(final DataSchemaNode potential) {
+        if (potential instanceof ContainerSchemaNode) {
+            return new Container((ContainerSchemaNode) potential);
+        } else if (potential instanceof ListSchemaNode) {
+            return fromListSchemaNode((ListSchemaNode) potential);
+        } else if (potential instanceof LeafSchemaNode) {
+            return new Leaf((LeafSchemaNode) potential);
+        } else if (potential instanceof ChoiceSchemaNode) {
+            return new Choice((ChoiceSchemaNode) potential);
+        } else if (potential instanceof LeafListSchemaNode) {
+            return fromLeafListSchemaNode((LeafListSchemaNode) potential);
+        } else if (potential instanceof AnyXmlSchemaNode) {
+            return new AnyXml((AnyXmlSchemaNode) potential);
+        }
+        return null;
+    }
+
+    @Override
+    public final T getIdentifier() {
+        return identifier;
+    }
+
+    abstract StreamingContext<?> getChild(PathArgument child);
+
+    abstract void streamToWriter(NormalizedNodeStreamWriter writer, PathArgument first, Iterator<PathArgument> others)
+            throws IOException;
+
+    abstract boolean isMixin();
+
+    private static Optional<DataSchemaNode> findChildSchemaNode(final DataNodeContainer parent, final QName child) {
+        DataSchemaNode potential = parent.getDataChildByName(child);
+        if (potential == null) {
+            potential = findChoice(Iterables.filter(parent.getChildNodes(), ChoiceSchemaNode.class), child);
+        }
+        return Optional.ofNullable(potential);
+    }
+
+    private static ChoiceSchemaNode findChoice(final Iterable<ChoiceSchemaNode> choices, final QName child) {
+        for (final ChoiceSchemaNode choice : choices) {
+            for (final CaseSchemaNode caze : choice.getCases().values()) {
+                if (findChildSchemaNode(caze, child).isPresent()) {
+                    return choice;
+                }
+            }
+        }
+        return null;
+    }
+
+    private static StreamingContext<?> fromListSchemaNode(final ListSchemaNode potential) {
+        final List<QName> keyDefinition = potential.getKeyDefinition();
+        if (keyDefinition == null || keyDefinition.isEmpty()) {
+            return new UnkeyedListMixin(potential);
+        }
+        return potential.isUserOrdered() ? new OrderedMapMixin(potential)
+                : new UnorderedMapMixin(potential);
+    }
+
+    private static StreamingContext<?> fromLeafListSchemaNode(final LeafListSchemaNode potential) {
+        return potential.isUserOrdered() ? new OrderedLeafListMixin(potential)
+                : new UnorderedLeafListMixin(potential);
+    }
+
+    private abstract static class AbstractComposite<T extends PathArgument> extends StreamingContext<T> {
+        AbstractComposite(final T identifier) {
+            super(identifier);
+        }
+
+        @Override
+        final void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
+                final Iterator<PathArgument> others) throws IOException {
+            if (!isMixin()) {
+                final QName type = getIdentifier().getNodeType();
+                if (type != null) {
+                    final QName firstType = first.getNodeType();
+                    checkArgument(type.equals(firstType), "Node QName must be %s was %s", type, firstType);
+                }
+            }
+
+            emitElementStart(writer, first);
+            if (others.hasNext()) {
+                final PathArgument childPath = others.next();
+                final StreamingContext<?> childOp = getChildOperation(childPath);
+                childOp.streamToWriter(writer, childPath, others);
+            }
+            writer.endNode();
+        }
+
+        abstract void emitElementStart(NormalizedNodeStreamWriter writer, PathArgument arg) throws IOException;
+
+        @SuppressWarnings("checkstyle:illegalCatch")
+        private StreamingContext<?> getChildOperation(final PathArgument childPath) {
+            final StreamingContext<?> childOp;
+            try {
+                childOp = getChild(childPath);
+            } catch (final RuntimeException e) {
+                throw new IllegalArgumentException(String.format("Failed to process child node %s", childPath), e);
+            }
+            checkArgument(childOp != null, "Node %s is not allowed inside %s", childPath, getIdentifier());
+            return childOp;
+        }
+    }
+
+    private abstract static class AbstractDataContainer<T extends PathArgument> extends AbstractComposite<T> {
+        private final Map<PathArgument, StreamingContext<?>> byArg = new HashMap<>();
+        private final DataNodeContainer schema;
+
+        AbstractDataContainer(final T identifier, final DataNodeContainer schema) {
+            super(identifier);
+            this.schema = schema;
+        }
+
+        @Override
+        final StreamingContext<?> getChild(final PathArgument child) {
+            StreamingContext<?> potential = byArg.get(child);
+            if (potential != null) {
+                return potential;
+            }
+            potential = fromLocalSchema(child);
+            if (potential != null) {
+                byArg.put(potential.getIdentifier(), potential);
+            }
+            return potential;
+        }
+
+        private StreamingContext<?> fromLocalSchema(final PathArgument child) {
+            if (child instanceof AugmentationIdentifier) {
+                return fromSchemaAndQNameChecked(schema, ((AugmentationIdentifier) child).getPossibleChildNames()
+                        .iterator().next());
+            }
+            return fromSchemaAndQNameChecked(schema, child.getNodeType());
+        }
+    }
+
+    private abstract static class AbstractMapMixin extends AbstractComposite<NodeIdentifier> {
+        private final ListEntry innerNode;
+
+        AbstractMapMixin(final ListSchemaNode list) {
+            super(NodeIdentifier.create(list.getQName()));
+            this.innerNode = new ListEntry(new NodeIdentifierWithPredicates(list.getQName(), ImmutableMap.of()), list);
+        }
+
+        @Override
+        final StreamingContext<?> getChild(final PathArgument child) {
+            return child.getNodeType().equals(getIdentifier().getNodeType()) ? innerNode : null;
+        }
+
+        @Override
+        final boolean isMixin() {
+            return true;
+        }
+    }
+
+    private abstract static class AbstractSimple<T extends PathArgument> extends StreamingContext<T> {
+        AbstractSimple(final T identifier) {
+            super(identifier);
+        }
+
+        @Override
+        final StreamingContext<?> getChild(final PathArgument child) {
+            return null;
+        }
+
+        @Override
+        final boolean isMixin() {
+            return false;
+        }
+    }
+
+    private static final class AnyXml extends AbstractSimple<NodeIdentifier> {
+        AnyXml(final AnyXmlSchemaNode schema) {
+            super(NodeIdentifier.create(schema.getQName()));
+        }
+
+        @Override
+        void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
+                final Iterator<PathArgument> others) throws IOException {
+            writer.anyxmlNode(getIdentifier(), null);
+        }
+    }
+
+    private static final class Choice extends AbstractComposite<NodeIdentifier> {
+        private final ImmutableMap<PathArgument, StreamingContext<?>> byArg;
+
+        Choice(final ChoiceSchemaNode schema) {
+            super(NodeIdentifier.create(schema.getQName()));
+            final ImmutableMap.Builder<PathArgument, StreamingContext<?>> byArgBuilder = ImmutableMap.builder();
+
+            for (final CaseSchemaNode caze : schema.getCases().values()) {
+                for (final DataSchemaNode cazeChild : caze.getChildNodes()) {
+                    final StreamingContext<?> childOp = fromDataSchemaNode(cazeChild);
+                    byArgBuilder.put(childOp.getIdentifier(), childOp);
+                }
+            }
+            byArg = byArgBuilder.build();
+        }
+
+        @Override
+        StreamingContext<?> getChild(final PathArgument child) {
+            return byArg.get(child);
+        }
+
+        @Override
+        boolean isMixin() {
+            return true;
+        }
+
+        @Override
+        void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
+            writer.startChoiceNode(getIdentifier(), UNKNOWN_SIZE);
+        }
+    }
+
+    private static final class Leaf extends AbstractSimple<NodeIdentifier> {
+        Leaf(final LeafSchemaNode potential) {
+            super(new NodeIdentifier(potential.getQName()));
+        }
+
+        @Override
+        void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
+                final Iterator<PathArgument> others) throws IOException {
+            writer.leafNode(getIdentifier(), null);
+        }
+    }
+
+    private static final class LeafListEntry extends AbstractSimple<NodeWithValue<?>> {
+        LeafListEntry(final LeafListSchemaNode potential) {
+            super(new NodeWithValue<>(potential.getQName(), null));
+        }
+
+        @Override
+        void streamToWriter(final NormalizedNodeStreamWriter writer, final PathArgument first,
+                final Iterator<PathArgument> others) throws IOException {
+            checkArgument(first instanceof NodeWithValue);
+            final NodeWithValue<?> identifier = (NodeWithValue<?>) first;
+            writer.leafSetEntryNode(identifier.getNodeType(), identifier.getValue());
+        }
+    }
+
+    private static final class ListEntry extends AbstractDataContainer<NodeIdentifierWithPredicates> {
+        ListEntry(final NodeIdentifierWithPredicates identifier, final ListSchemaNode schema) {
+            super(identifier, schema);
+        }
+
+        @Override
+        boolean isMixin() {
+            return false;
+        }
+
+        @Override
+        void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
+            final NodeIdentifierWithPredicates identifier = (NodeIdentifierWithPredicates) arg;
+            writer.startMapEntryNode(identifier, UNKNOWN_SIZE);
+
+            for (Entry<QName, Object> entry : identifier.getKeyValues().entrySet()) {
+                writer.leafNode(new NodeIdentifier(entry.getKey()), entry.getValue());
+            }
+        }
+    }
+
+    private static final class UnkeyedListItem extends AbstractDataContainer<NodeIdentifier> {
+        UnkeyedListItem(final ListSchemaNode schema) {
+            super(NodeIdentifier.create(schema.getQName()), schema);
+        }
+
+        @Override
+        boolean isMixin() {
+            return false;
+        }
+
+        @Override
+        void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
+            writer.startUnkeyedListItem(getIdentifier(), UNKNOWN_SIZE);
+        }
+    }
+
+    private static final class Container extends AbstractDataContainer<NodeIdentifier> {
+        Container(final ContainerSchemaNode schema) {
+            super(NodeIdentifier.create(schema.getQName()), schema);
+        }
+
+        @Override
+        boolean isMixin() {
+            return false;
+        }
+
+        @Override
+        void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
+            writer.startContainerNode(getIdentifier(), UNKNOWN_SIZE);
+        }
+    }
+
+    private abstract static class LeafListMixin extends AbstractComposite<NodeIdentifier> {
+        private final StreamingContext<?> innerOp;
+
+        LeafListMixin(final LeafListSchemaNode potential) {
+            super(NodeIdentifier.create(potential.getQName()));
+            innerOp = new LeafListEntry(potential);
+        }
+
+        @Override
+        final StreamingContext<?> getChild(final PathArgument child) {
+            return child instanceof NodeWithValue ? innerOp : null;
+        }
+
+        @Override
+        final boolean isMixin() {
+            return true;
+        }
+    }
+
+    private static final class OrderedLeafListMixin extends LeafListMixin {
+        OrderedLeafListMixin(final LeafListSchemaNode potential) {
+            super(potential);
+        }
+
+        @Override
+        void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
+            writer.startOrderedLeafSet(getIdentifier(), UNKNOWN_SIZE);
+        }
+    }
+
+    private static class UnorderedLeafListMixin extends LeafListMixin {
+        UnorderedLeafListMixin(final LeafListSchemaNode potential) {
+            super(potential);
+        }
+
+        @Override
+        void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
+            writer.startLeafSet(getIdentifier(), UNKNOWN_SIZE);
+        }
+    }
+
+    private static final class Augmentation extends AbstractDataContainer<AugmentationIdentifier> {
+        Augmentation(final AugmentationSchemaNode augmentation, final DataNodeContainer schema) {
+            super(DataSchemaContextNode.augmentationIdentifierFrom(augmentation),
+                    EffectiveAugmentationSchema.create(augmentation, schema));
+        }
+
+        @Override
+        boolean isMixin() {
+            return true;
+        }
+
+        @Override
+        void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
+            writer.startAugmentationNode(getIdentifier());
+        }
+    }
+
+    private static final class UnorderedMapMixin extends AbstractMapMixin {
+        UnorderedMapMixin(final ListSchemaNode list) {
+            super(list);
+        }
+
+        @Override
+        void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
+            writer.startMapNode(getIdentifier(), UNKNOWN_SIZE);
+        }
+    }
+
+    private static final class OrderedMapMixin extends AbstractMapMixin {
+        OrderedMapMixin(final ListSchemaNode list) {
+            super(list);
+        }
+
+        @Override
+        void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
+            writer.startOrderedMapNode(getIdentifier(), UNKNOWN_SIZE);
+        }
+    }
+
+    private static final class UnkeyedListMixin extends AbstractComposite<NodeIdentifier> {
+        private final UnkeyedListItem innerNode;
+
+        UnkeyedListMixin(final ListSchemaNode list) {
+            super(NodeIdentifier.create(list.getQName()));
+            this.innerNode = new UnkeyedListItem(list);
+        }
+
+        @Override
+        StreamingContext<?> getChild(final PathArgument child) {
+            return child.getNodeType().equals(getIdentifier().getNodeType()) ? innerNode : null;
+        }
+
+        @Override
+        boolean isMixin() {
+            return true;
+        }
+
+        @Override
+        void emitElementStart(final NormalizedNodeStreamWriter writer, final PathArgument arg) throws IOException {
+            writer.startUnkeyedList(getIdentifier(), UNKNOWN_SIZE);
+        }
+    }
+}
index ea7abe2f74291eec22fa1848dc6575c700634407..9275020f3d40ffc755daee5f6cb4244a61b67497 100644 (file)
@@ -209,15 +209,13 @@ public final class NetconfMessageTransformUtil {
                                                              final SchemaContext ctx) {
         final NormalizedNodeAttrBuilder<NodeIdentifier, DOMSource, AnyXmlNode> anyXmlBuilder = Builders.anyXmlBuilder()
                 .withNodeIdentifier(NETCONF_FILTER_NODEID).withAttributes(SUBTREE_FILTER_ATTRIBUTES);
-        final NormalizedNode<?, ?> filterContent = ImmutableNodes.fromInstanceId(ctx, identifier);
-
         final Element element = XmlUtil.createElement(BLANK_DOCUMENT, NETCONF_FILTER_QNAME.getLocalName(),
                 Optional.of(NETCONF_FILTER_QNAME.getNamespace().toString()));
         element.setAttributeNS(NETCONF_FILTER_QNAME.getNamespace().toString(), NETCONF_TYPE_QNAME.getLocalName(),
                 "subtree");
 
         try {
-            NetconfUtil.writeNormalizedNode(filterContent, new DOMResult(element), SchemaPath.ROOT, ctx);
+            NetconfUtil.writeFilter(identifier, new DOMResult(element), SchemaPath.ROOT, ctx);
         } catch (IOException | XMLStreamException e) {
             throw new IllegalStateException("Unable to serialize filter element for path " + identifier, e);
         }
index 96a59a18bfd1097783aa2f4378edbc41058ce77c..af0a8b238e1a0069d74242304e417a5fd7290ba4 100644 (file)
@@ -5,7 +5,6 @@
  * 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.netconf.sal.connect.netconf.schema.mapping;
 
 import static org.junit.Assert.assertEquals;
@@ -31,6 +30,7 @@ import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTr
 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.toPath;
 
 import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
@@ -234,6 +234,36 @@ public class NetconfMessageTransformerTest {
         assertEquals(schemaNode, schemaParent.getValue().iterator().next());
     }
 
+    @Test
+    public void testGetConfigLeafRequest() throws Exception {
+        final DataContainerChild<?, ?> filter = toFilterStructure(
+                YangInstanceIdentifier.create(toId(NetconfState.QNAME), toId(Schemas.QNAME), toId(Schema.QNAME),
+                    new NodeIdentifierWithPredicates(Schema.QNAME, ImmutableMap.of()),
+                    toId(QName.create(Schemas.QNAME, "version"))), schema);
+
+        final DataContainerChild<?, ?> source = NetconfBaseOps.getSourceNode(NETCONF_RUNNING_QNAME);
+
+        final NetconfMessage netconfMessage = netconfMessageTransformer.toRpcRequest(toPath(NETCONF_GET_CONFIG_QNAME),
+                NetconfMessageTransformUtil.wrap(NETCONF_GET_CONFIG_QNAME, source, filter));
+
+        assertSimilarXml(netconfMessage, "<rpc message-id=\"m-0\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n"
+                + "<get-config xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n"
+                + "<filter xmlns:ns0=\"urn:ietf:params:xml:ns:netconf:base:1.0\" ns0:type=\"subtree\">\n"
+                + "<netconf-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring\">\n"
+                + "<schemas>\n"
+                + "<schema>\n"
+                + "<version/>\n"
+                + "</schema>\n"
+                + "</schemas>\n"
+                + "</netconf-state>\n"
+                + "</filter>\n"
+                + "<source>\n"
+                + "<running/>\n"
+                + "</source>\n"
+                + "</get-config>\n"
+                + "</rpc>");
+    }
+
     @Test
     public void testGetConfigRequest() throws Exception {
         final DataContainerChild<?, ?> filter = toFilterStructure(
@@ -477,8 +507,8 @@ public class NetconfMessageTransformerTest {
         assertEquals("now", leaf.getValue());
     }
 
-    private void checkAction(QName actionQname, Node action , String inputLocalName, String inputNodeName,
-            String inputValue) {
+    private void checkAction(final QName actionQname, final Node action , final String inputLocalName,
+            final String inputNodeName, final String inputValue) {
         checkNode(action, null, actionQname.getLocalName(), null);
 
         Node childResetAt = action.getFirstChild();
@@ -488,7 +518,7 @@ public class NetconfMessageTransformerTest {
         assertEquals(firstChild.getData(), inputValue);
     }
 
-    private Node checkBasePartOfActionRequest(NetconfMessage actionRequest) {
+    private Node checkBasePartOfActionRequest(final NetconfMessage actionRequest) {
         Node baseRpc = actionRequest.getDocument().getFirstChild();
         checkNode(baseRpc, "rpc", "rpc", NetconfMessageTransformUtil.NETCONF_QNAME.getNamespace().toString());
         assertTrue(baseRpc.getLocalName().equals("rpc"));
@@ -503,7 +533,7 @@ public class NetconfMessageTransformerTest {
         return childAction;
     }
 
-    private DOMDataTreeIdentifier prepareDataTreeId(Set<PathArgument> nodeIdentifiers) {
+    private DOMDataTreeIdentifier prepareDataTreeId(final Set<PathArgument> nodeIdentifiers) {
         YangInstanceIdentifier yangInstanceIdentifier =
                 YangInstanceIdentifier.builder().append(nodeIdentifiers).build();
         DOMDataTreeIdentifier domDataTreeIdentifier =
@@ -512,7 +542,7 @@ public class NetconfMessageTransformerTest {
         return domDataTreeIdentifier;
     }
 
-    private ContainerNode initInputAction(QName qname, String value) {
+    private ContainerNode initInputAction(final QName qname, final String value) {
         ImmutableLeafNodeBuilder<String> immutableLeafNodeBuilder = new ImmutableLeafNodeBuilder<>();
         DataContainerChild<NodeIdentifier, String> build = immutableLeafNodeBuilder.withNodeIdentifier(
                 NodeIdentifier.create(qname)).withValue(value).build();
@@ -521,8 +551,8 @@ public class NetconfMessageTransformerTest {
         return data;
     }
 
-    private void checkNode(Node childServer, String expectedLocalName, String expectedNodeName,
-            String expectedNamespace) {
+    private void checkNode(final Node childServer, final String expectedLocalName, final String expectedNodeName,
+            final String expectedNamespace) {
         assertNotNull(childServer);
         assertEquals(childServer.getLocalName(), expectedLocalName);
         assertEquals(childServer.getNodeName(), expectedNodeName);