Merge "Stop using blocking invoke for lock and editConfig."
authorTomas Cere <tcere@cisco.com>
Tue, 21 Jun 2016 10:31:17 +0000 (10:31 +0000)
committerGerrit Code Review <gerrit@opendaylight.org>
Tue, 21 Jun 2016 10:31:17 +0000 (10:31 +0000)
27 files changed:
netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/impl/ClusteredNetconfTopology.java
netconf/netconf-topology/src/main/java/org/opendaylight/netconf/topology/pipeline/ClusteredNetconfDevice.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/SchemalessNetconfDevice.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/SchemalessNetconfDeviceRpc.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/schema/mapping/NetconfMessageTransformer.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/schema/mapping/SchemalessMessageTransformer.java
netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/SchemalessNetconfDeviceRpcTest.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/utils/parser/ParserIdentifier.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/parser/builder/IdentifierCodecTest.java [new file with mode: 0644]
restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/parser/builder/YangInstanceIdentifierDeserializerTest.java [new file with mode: 0644]
restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/parser/builder/YangInstanceIdentifierSerializerTest.java
restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/utils/parser/ParserIdentifierTest.java [new file with mode: 0644]
restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/utils/schema/context/RestconfSchemaUtilTest.java [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/modules/restconf-module-testing/restconf-module-with-empty-grouping-restconf.yang [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/modules/restconf-module-testing/restconf-module-with-missing-container-modules.yang [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/modules/restconf-module-testing/restconf-module-with-missing-grouping-restconf.yang [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/modules/restconf-module-testing/restconf-module-with-missing-list-module.yang [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/modules/restconf-module-testing/restconf-module-with-missing-list-stream.yang
restconf/sal-rest-connector/src/test/resources/parser-identifier/mount-point.yang [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/parser-identifier/parser-identifier-test-included.yang [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/parser-identifier/parser-identifier-test.yang [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/parser-identifier/test-module.yang [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/restconf/parser/deserializer/deserializer-test-included.yang [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/restconf/parser/deserializer/deserializer-test.yang [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/restconf/parser/list-test.yang
restconf/sal-rest-connector/src/test/resources/restconf/parser/serializer/serializer-test-included.yang [new file with mode: 0644]
restconf/sal-rest-connector/src/test/resources/restconf/parser/serializer/serializer-test.yang [new file with mode: 0644]

index ec6e4fec333b8921c00d4d6e57d12de75674dc51..9815231653ab4272fc50f3c7cad672c0b29bea3f 100644 (file)
@@ -159,7 +159,8 @@ public class ClusteredNetconfTopology extends AbstractNetconfTopology implements
         final NetconfDevice.SchemaResourcesDTO schemaResourcesDTO = setupSchemaCacheDTO(nodeId, node);
 
         final NetconfDevice device = new ClusteredNetconfDevice(schemaResourcesDTO, remoteDeviceId, salFacade,
-                processingExecutor.getExecutor(), actorSystem, topologyId, nodeId.getValue(), TypedActor.context());
+                processingExecutor.getExecutor(), actorSystem, topologyId, nodeId.getValue(), TypedActor.context(),
+                reconnectOnChangedSchema);
 
         final int rpcMessageLimit =
                 node.getConcurrentRpcLimit() == null ? DEFAULT_CONCURRENT_RPC_LIMIT : node.getConcurrentRpcLimit();
index 0710769c8c9ead597add94654853b772e1ab78cc..cf0a8577ba17026132defc29606561f6f447ffbd 100644 (file)
@@ -61,8 +61,8 @@ public class ClusteredNetconfDevice extends NetconfDevice implements EntityOwner
 
     public ClusteredNetconfDevice(final SchemaResourcesDTO schemaResourcesDTO, final RemoteDeviceId id, final RemoteDeviceHandler<NetconfSessionPreferences> salFacade,
                                   final ExecutorService globalProcessingExecutor, final ActorSystem actorSystem, final String topologyId, final String nodeId,
-                                  ActorContext cachedContext) {
-        super(schemaResourcesDTO, id, salFacade, globalProcessingExecutor, false);
+                                  final ActorContext cachedContext, final boolean reconnectOnSchemaChanged) {
+        super(schemaResourcesDTO, id, salFacade, globalProcessingExecutor, reconnectOnSchemaChanged);
         this.schemaRepo = (SchemaRepository) schemaResourcesDTO.getSchemaRegistry();
         this.actorSystem = actorSystem;
         this.topologyId = topologyId;
index 74e64354e10475d9710e81ff86c588e2c0cdad04..758c46c80df8a9d73a6160a7dd988fe26c145f25 100644 (file)
@@ -13,7 +13,10 @@ import org.opendaylight.netconf.sal.connect.api.RemoteDeviceHandler;
 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfDeviceCommunicator;
 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
 import org.opendaylight.netconf.sal.connect.netconf.sal.SchemalessNetconfDeviceRpc;
+import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.BaseRpcSchemalessTransformer;
 import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.BaseSchema;
+import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.SchemalessMessageTransformer;
+import org.opendaylight.netconf.sal.connect.util.MessageCounter;
 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
 
 public class SchemalessNetconfDevice implements
@@ -21,18 +24,22 @@ public class SchemalessNetconfDevice implements
 
     private RemoteDeviceId id;
     private RemoteDeviceHandler<NetconfSessionPreferences> salFacade;
+    private final SchemalessMessageTransformer messageTransformer;
+    private final BaseRpcSchemalessTransformer rpcTransformer;
 
     public SchemalessNetconfDevice(final RemoteDeviceId id,
                                    final RemoteDeviceHandler<NetconfSessionPreferences> salFacade) {
         this.id = id;
         this.salFacade = salFacade;
+        final MessageCounter counter = new MessageCounter();
+        rpcTransformer = new BaseRpcSchemalessTransformer(counter);
+        messageTransformer = new SchemalessMessageTransformer(counter);
     }
 
     @Override public void onRemoteSessionUp(final NetconfSessionPreferences remoteSessionCapabilities,
                                             final NetconfDeviceCommunicator netconfDeviceCommunicator) {
-
         final SchemalessNetconfDeviceRpc schemalessNetconfDeviceRpc = new SchemalessNetconfDeviceRpc(id,
-                netconfDeviceCommunicator);
+                netconfDeviceCommunicator, rpcTransformer, messageTransformer);
 
         salFacade.onDeviceConnected(BaseSchema.BASE_NETCONF_CTX.getSchemaContext(),
                 remoteSessionCapabilities, schemalessNetconfDeviceRpc);
@@ -48,6 +55,6 @@ public class SchemalessNetconfDevice implements
     }
 
     @Override public void onNotification(final NetconfMessage notification) {
-        // TODO support for notifications
+        salFacade.onNotification(messageTransformer.toNotification(notification));
     }
 }
index d610904e6b76f2e34144d6c1a1f78a0c640e192b..68d20c7e42159b2a1b13d844f672c4f953c58bd7 100644 (file)
@@ -25,7 +25,6 @@ import org.opendaylight.netconf.sal.connect.api.RemoteDeviceCommunicator;
 import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.BaseRpcSchemalessTransformer;
 import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.SchemalessMessageTransformer;
 import org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil;
-import org.opendaylight.netconf.sal.connect.util.MessageCounter;
 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
 import org.opendaylight.yangtools.concepts.ListenerRegistration;
 import org.opendaylight.yangtools.yang.common.RpcResult;
@@ -45,13 +44,13 @@ public final class SchemalessNetconfDeviceRpc implements DOMRpcService {
     private final SchemalessMessageTransformer schemalessTransformer;
     private final RemoteDeviceId deviceId;
 
-    public SchemalessNetconfDeviceRpc(final RemoteDeviceId deviceId,
-                                      final RemoteDeviceCommunicator<NetconfMessage> listener) {
+    public SchemalessNetconfDeviceRpc(RemoteDeviceId deviceId, final RemoteDeviceCommunicator<NetconfMessage> listener,
+                                      final BaseRpcSchemalessTransformer baseRpcTransformer,
+                                      final SchemalessMessageTransformer messageTransformer) {
         this.deviceId = deviceId;
         this.listener = listener;
-        final MessageCounter counter = new MessageCounter();
-        baseRpcTransformer = new BaseRpcSchemalessTransformer(counter);
-        schemalessTransformer = new SchemalessMessageTransformer(counter);
+        this.baseRpcTransformer = baseRpcTransformer;
+        this.schemalessTransformer = messageTransformer;
     }
 
     @Nonnull
index bf8249fa8474b1cbe149b9401a2500e2358b7a2c..0abd74409709240a7b9ab13620e9b484b096b1b2 100644 (file)
@@ -9,6 +9,7 @@ package org.opendaylight.netconf.sal.connect.netconf.schema.mapping;
 
 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.IETF_NETCONF_NOTIFICATIONS;
 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_URI;
+import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.stripNotification;
 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.toPath;
 
 import com.google.common.base.Function;
@@ -222,7 +223,7 @@ public class NetconfMessageTransformer implements MessageTransformer<NetconfMess
         return new DefaultDOMRpcResult(normalizedNode);
     }
 
-    private static class NetconfDeviceNotification implements DOMNotification, DOMEvent {
+    static class NetconfDeviceNotification implements DOMNotification, DOMEvent {
         private final ContainerNode content;
         private final SchemaPath schemaPath;
         private final Date eventTime;
index 04c502399d1e7b58eec6c20864b7b3a396754a31..00fb9b4645d55a37b2e82132e2ae6b732a048d7f 100644 (file)
@@ -7,7 +7,11 @@
  */
 package org.opendaylight.netconf.sal.connect.netconf.schema.mapping;
 
+import java.util.Date;
+import java.util.Map;
 import javax.xml.transform.dom.DOMSource;
+import org.opendaylight.controller.config.util.xml.MissingNameSpaceException;
+import org.opendaylight.controller.config.util.xml.XmlElement;
 import org.opendaylight.controller.md.sal.dom.api.DOMNotification;
 import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
 import org.opendaylight.controller.md.sal.dom.spi.DefaultDOMRpcResult;
@@ -15,8 +19,10 @@ import org.opendaylight.netconf.api.NetconfMessage;
 import org.opendaylight.netconf.sal.connect.api.MessageTransformer;
 import org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil;
 import org.opendaylight.netconf.sal.connect.util.MessageCounter;
+import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
@@ -30,6 +36,10 @@ public class SchemalessMessageTransformer implements MessageTransformer<NetconfM
 
     private static final YangInstanceIdentifier.NodeIdentifier REPLY_ID =
             new YangInstanceIdentifier.NodeIdentifier(NetconfMessageTransformUtil.NETCONF_RPC_REPLY_QNAME);
+    // TODO maybe we should move this somewhere else as this
+    // might be used in applications using schemaless mountpoints
+    public static final YangInstanceIdentifier.NodeIdentifier SCHEMALESS_NOTIFICATION_PAYLOAD =
+            new YangInstanceIdentifier.NodeIdentifier(QName.create("schemaless-notification-payload"));
 
     private final MessageCounter counter;
 
@@ -39,8 +49,26 @@ public class SchemalessMessageTransformer implements MessageTransformer<NetconfM
 
     @Override
     public DOMNotification toNotification(final NetconfMessage message) {
-        //TODO add support for notifications
-        throw new UnsupportedOperationException("Notifications not supported.");
+        final Map.Entry<Date, XmlElement> stripped = NetconfMessageTransformUtil.stripNotification(message);
+        final QName notificationNoRev;
+        try {
+            notificationNoRev =
+                    QName.create(stripped.getValue().getNamespace(), stripped.getValue().getName()).withoutRevision();
+        } catch (final MissingNameSpaceException e) {
+            throw new IllegalArgumentException("Unable to parse notification " + message + ", cannot find namespace", e);
+        }
+
+        final AnyXmlNode notificationPayload = Builders.anyXmlBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(notificationNoRev))
+                .withValue(new DOMSource(stripped.getValue().getDomElement()))
+                .build();
+
+        final ContainerNode notificationBody = Builders.containerBuilder()
+                .withNodeIdentifier(SCHEMALESS_NOTIFICATION_PAYLOAD)
+                .withChild(notificationPayload)
+                .build();
+
+        return new NetconfMessageTransformer.NetconfDeviceNotification(notificationBody, stripped.getKey());
     }
 
     @Override
index 575b7b96994d92307d8b8f9f9f309b9d13376a4b..237021930a9a28457315bee4f05dfe3ace3a2215 100644 (file)
@@ -16,6 +16,9 @@ import org.mockito.MockitoAnnotations;
 import org.opendaylight.controller.config.util.xml.XmlUtil;
 import org.opendaylight.netconf.api.NetconfMessage;
 import org.opendaylight.netconf.sal.connect.api.RemoteDeviceCommunicator;
+import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.BaseRpcSchemalessTransformer;
+import org.opendaylight.netconf.sal.connect.netconf.schema.mapping.SchemalessMessageTransformer;
+import org.opendaylight.netconf.sal.connect.util.MessageCounter;
 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.RpcResult;
@@ -37,7 +40,10 @@ public class SchemalessNetconfDeviceRpcTest {
         RpcResult<NetconfMessage> msg = null;
         ListenableFuture<RpcResult<NetconfMessage>> future = Futures.immediateFuture(msg);
         doReturn(future).when(listener).sendRequest(any(), any());
-        deviceRpc = new SchemalessNetconfDeviceRpc(new RemoteDeviceId("device1", InetSocketAddress.createUnresolved("0.0.0.0", 17830)), listener);
+        final MessageCounter counter = new MessageCounter();
+        deviceRpc = new SchemalessNetconfDeviceRpc(
+                new RemoteDeviceId("device1", InetSocketAddress.createUnresolved("0.0.0.0", 17830)), listener,
+                new BaseRpcSchemalessTransformer(counter), new SchemalessMessageTransformer(counter));
 
     }
 
index 9f1dcb4e066c0e370086af918b944db2f181a800..891784a29903aa7e3f27fc64548c9ae94f0a6f84 100644 (file)
@@ -72,18 +72,28 @@ public final class ParserIdentifier {
      * @return {@link QName}
      */
     public static QName makeQNameFromIdentifier(final String identifier) {
+        // check if more than one slash is not used as path separator
+        if (identifier.contains(
+                String.valueOf(RestconfConstants.SLASH).concat(String.valueOf(RestconfConstants.SLASH)))) {
+            LOG.debug("URI has bad format. It should be \'moduleName/yyyy-MM-dd\' " + identifier);
+            throw new RestconfDocumentedException(
+                    "URI has bad format. End of URI should be in format \'moduleName/yyyy-MM-dd\'", ErrorType.PROTOCOL,
+                    ErrorTag.INVALID_VALUE);
+        }
+
         final int mountIndex = identifier.indexOf(RestconfConstants.MOUNT);
         String moduleNameAndRevision = "";
         if (mountIndex >= 0) {
-            moduleNameAndRevision = identifier.substring(mountIndex + RestconfConstants.MOUNT.length());
+            moduleNameAndRevision = identifier.substring(mountIndex + RestconfConstants.MOUNT.length())
+                    .replaceFirst(String.valueOf(RestconfConstants.SLASH), "");
         } else {
             moduleNameAndRevision = identifier;
         }
 
-        final Splitter splitter = Splitter.on("/").omitEmptyStrings();
+        final Splitter splitter = Splitter.on(RestconfConstants.SLASH);
         final Iterable<String> split = splitter.split(moduleNameAndRevision);
-        final List<String> pathArgs = Lists.<String> newArrayList(split);
-        if (pathArgs.size() < 2) {
+        final List<String> pathArgs = Lists.newArrayList(split);
+        if (pathArgs.size() != 2) {
             LOG.debug("URI has bad format. It should be \'moduleName/yyyy-MM-dd\' " + identifier);
             throw new RestconfDocumentedException(
                     "URI has bad format. End of URI should be in format \'moduleName/yyyy-MM-dd\'", ErrorType.PROTOCOL,
diff --git a/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/parser/builder/IdentifierCodecTest.java b/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/parser/builder/IdentifierCodecTest.java
new file mode 100644 (file)
index 0000000..a10eafe
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. 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.restconf.parser.builder;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils;
+import org.opendaylight.restconf.parser.IdentifierCodec;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+/**
+ * Unit tests for {@link IdentifierCodec} mostly according to examples from draft-ietf-netconf-restconf-13
+ */
+public class IdentifierCodecTest {
+
+    private static final String URI_WITH_LIST_AND_LEAF =
+            "/list-test:top/list1=%2C%27" + '"' + "%3A" + '"' + "%20%2F,,foo/list2=a,b/result";
+    private static final String URI_WITH_LEAF_LIST = "/list-test:top/Y=x%3Ay";
+
+    private SchemaContext schemaContext;
+
+    @Before
+    public void init() throws Exception {
+        this.schemaContext = TestRestconfUtils.loadSchemaContext("/restconf/parser");
+    }
+
+    /**
+     * Positive test of deserialization URI <code>String</code> to <code>YangInstanceIdentifier</code> and
+     * serialization of <code>YangInstanceIdentifier</code> to <code>String/code> when original <code>String</code>
+     * URI contains list identifier and leaf identifier.
+     */
+    @Test
+    public void codecListAndLeafTest() {
+        final YangInstanceIdentifier dataYangII = IdentifierCodec.deserialize(
+                this.URI_WITH_LIST_AND_LEAF, this.schemaContext);
+        final String serializedDataYangII = IdentifierCodec.serialize(dataYangII, this.schemaContext);
+
+        assertEquals("Failed codec deserialization and serialization test",
+                this.URI_WITH_LIST_AND_LEAF, serializedDataYangII);
+    }
+
+    /**
+     * Positive test of deserialization URI <code>String</code> to <code>YangInstanceIdentifier</code> and
+     * serialization of <code>YangInstanceIdentifier</code> to <code>String/code> when original <code>String</code>
+     * URI contains leaf list identifier.
+     */
+    @Test
+    public void codecLeafListTest() {
+        final YangInstanceIdentifier dataYangII = IdentifierCodec.deserialize(
+                this.URI_WITH_LEAF_LIST, this.schemaContext);
+        final String serializedDataYangII = IdentifierCodec.serialize(dataYangII, this.schemaContext);
+
+        assertEquals("Failed codec deserialization and serialization test",
+                this.URI_WITH_LEAF_LIST, serializedDataYangII);
+    }
+
+    /**
+     * Positive test of deserialization URI <code>String</code> to <code>YangInstanceIdentifier</code> when
+     * <code>String</code> URI is <code>null</code>. <code>YangInstanceIdentifier.EMPTY</code> is
+     * expected to be returned.
+     */
+    @Test
+    public void codecDeserializeNullTest () {
+        final YangInstanceIdentifier dataYangII = IdentifierCodec.deserialize(null, this.schemaContext);
+        assertEquals("Failed codec deserialization test", YangInstanceIdentifier.EMPTY, dataYangII);
+    }
+
+    /**
+     * Positive test of serialization <code>YangInstanceIdentifier.EMPTY</code>. Single slash is
+     * expected to be returned.
+     */
+    @Test
+    public void codecSerializeEmptyTest () {
+        final String serialized = IdentifierCodec.serialize(YangInstanceIdentifier.EMPTY, this.schemaContext);
+        assertEquals("Failed codec serialization test", "/", serialized);
+    }
+
+    /**
+     * Positive test of serialization <code>YangInstanceIdentifier.EMPTY</code> and deserialization of result back to
+     * <code>YangInstanceIdentifier.EMPTY</code>.
+     */
+    @Test
+    public void codecDeserializeAndSerializeEmptyTest() {
+        final String serialized = IdentifierCodec.serialize(YangInstanceIdentifier.EMPTY, this.schemaContext);
+        assertEquals("Failed codec serialization and deserialization test",
+                YangInstanceIdentifier.EMPTY, IdentifierCodec.deserialize(serialized, this.schemaContext));
+    }
+}
diff --git a/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/parser/builder/YangInstanceIdentifierDeserializerTest.java b/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/parser/builder/YangInstanceIdentifierDeserializerTest.java
new file mode 100644 (file)
index 0000000..7ba1bac
--- /dev/null
@@ -0,0 +1,577 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. 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.restconf.parser.builder;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+/**
+ * Unit tests for {@link YangInstanceIdentifierDeserializer}
+ */
+public class YangInstanceIdentifierDeserializerTest {
+
+    @Rule
+    public ExpectedException thrown = ExpectedException.none();
+
+    // schema context
+    private SchemaContext schemaContext;
+
+    @Before
+    public void init() throws Exception {
+        schemaContext = TestRestconfUtils.loadSchemaContext("/restconf/parser/deserializer");
+    }
+
+    /**
+     * Test of deserialization <code>String</code> URI with container to
+     * <code>Iterable<YangInstanceIdentifier.PathArgument></code>.
+     */
+    @Test
+    public void deserializeContainerTest() {
+        final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
+                .create(schemaContext, "/deserializer-test:contA");
+
+        assertEquals("Result does not contains expected number of path arguments", 1, Iterables.size(result));
+        assertEquals("Not expected path argument",
+                YangInstanceIdentifier.NodeIdentifier.create(QName.create("deserializer:test", "2016-06-06", "contA")),
+                result.iterator().next());
+    }
+
+    /**
+     * Test of deserialization <code>String</code> URI with container containing leaf to
+     * <code>Iterable<YangInstanceIdentifier.PathArgument></code>.
+     */
+    @Test
+    public void deserializeContainerWithLeafTest() {
+        final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
+                .create(schemaContext, "/deserializer-test:contA/leaf-A");
+
+        assertEquals("Result does not contains expected number of path arguments", 2, Iterables.size(result));
+
+        final Iterator<YangInstanceIdentifier.PathArgument> iterator = result.iterator();
+        assertEquals("Not expected path argument",
+                YangInstanceIdentifier.NodeIdentifier.create(QName.create("deserializer:test", "2016-06-06", "contA")),
+                iterator.next());
+        assertEquals("Not expected path argument",
+                YangInstanceIdentifier.NodeIdentifier.create(QName.create("deserializer:test", "2016-06-06", "leaf-A")),
+                iterator.next());
+    }
+
+    /**
+     * Test of deserialization <code>String</code> URI with container containing list with leaf list to
+     * <code>Iterable<YangInstanceIdentifier.PathArgument></code>.
+     */
+    @Test
+    public void deserializeContainerWithListWithLeafListTest() {
+        final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
+                .create(schemaContext, "/deserializer-test:contA/list-A=100/leaf-list-AA=instance");
+
+        assertEquals("Result does not contains expected number of path arguments", 5, Iterables.size(result));
+
+        final Iterator<YangInstanceIdentifier.PathArgument> iterator = result.iterator();
+
+        // container
+        assertEquals("Not expected path argument",
+                YangInstanceIdentifier.NodeIdentifier.create(QName.create("deserializer:test", "2016-06-06", "contA")),
+                iterator.next());
+
+        // list
+        final QName list = QName.create("deserializer:test", "2016-06-06", "list-A");
+        assertEquals("Not expected path argument",
+                YangInstanceIdentifier.NodeIdentifier.create(list),
+                iterator.next());
+        assertEquals("Not expected path argument",
+                new YangInstanceIdentifier.NodeIdentifierWithPredicates(
+                        list, QName.create(list, "list-key"), 100).toString(),
+                iterator.next().toString());
+
+        // leaf list
+        final QName leafList = QName.create("deserializer:test", "2016-06-06", "leaf-list-AA");
+        assertEquals("Not expected path argument",
+                YangInstanceIdentifier.NodeIdentifier.create(leafList),
+                iterator.next());
+        assertEquals("Not expected path argument",
+                new YangInstanceIdentifier.NodeWithValue(leafList, "instance"),
+                iterator.next());
+    }
+
+    /**
+     * Test of deserialization <code>String</code> URI containing list with no keys to
+     * <code>Iterable<YangInstanceIdentifier.PathArgument></code>.
+     */
+    @Test
+    public void deserializeListWithNoKeysTest() {
+        final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
+                .create(schemaContext, "/deserializer-test:list-no-key");
+
+        assertEquals("Result does not contains expected number of path arguments", 2, Iterables.size(result));
+
+        final Iterator<YangInstanceIdentifier.PathArgument> iterator = result.iterator();
+        final QName list = QName.create("deserializer:test", "2016-06-06", "list-no-key");
+
+        assertEquals("Not expected path argument",
+                YangInstanceIdentifier.NodeIdentifier.create(list),
+                iterator.next());
+        assertEquals("Not expected path argument",
+                YangInstanceIdentifier.NodeIdentifier.create(list),
+                iterator.next());
+    }
+
+    /**
+     * Test of deserialization <code>String</code> URI containing list with one key to
+     * <code>Iterable<YangInstanceIdentifier.PathArgument></code>.
+     */
+    @Test
+    public void deserializeListWithOneKeyTest() {
+        final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
+                .create(schemaContext, "/deserializer-test:list-one-key=value");
+
+        assertEquals("Result does not contains expected number of path arguments", 2, Iterables.size(result));
+
+        final Iterator<YangInstanceIdentifier.PathArgument> iterator = result.iterator();
+        final QName list = QName.create("deserializer:test", "2016-06-06", "list-one-key");
+
+        assertEquals("Not expected path argument",
+                YangInstanceIdentifier.NodeIdentifier.create(list),
+                iterator.next());
+        assertEquals("Not expected path argument",
+                new YangInstanceIdentifier.NodeIdentifierWithPredicates(list, QName.create(list, "name"), "value"),
+                iterator.next());
+    }
+
+    /**
+     * Test of deserialization <code>String</code> URI containing list with multiple keys to
+     * <code>Iterable<YangInstanceIdentifier.PathArgument></code>.
+     */
+    @Test
+    public void deserializeListWithMultipleKeysTest() {
+        final QName list = QName.create("deserializer:test", "2016-06-06", "list-multiple-keys");
+        final Map<QName, Object> values = new LinkedHashMap<>();
+        values.put(QName.create(list, "name"), "value");
+        values.put(QName.create(list, "number"), 100);
+        values.put(QName.create(list, "enabled"), false);
+
+        final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
+                .create(schemaContext, "/deserializer-test:list-multiple-keys=value,100,false");
+
+        assertEquals("Result does not contains expected number of path arguments", 2, Iterables.size(result));
+
+        final Iterator<YangInstanceIdentifier.PathArgument> iterator = result.iterator();
+
+        assertEquals("Not expected path argument",
+                YangInstanceIdentifier.NodeIdentifier.create(list),
+                iterator.next());
+        assertEquals("Not expected path argument",
+                new YangInstanceIdentifier.NodeIdentifierWithPredicates(list, values).toString(),
+                iterator.next().toString());
+    }
+
+    /**
+     * Test of deserialization <code>String</code> URI containing leaf list to
+     * <code>Iterable<YangInstanceIdentifier.PathArgument></code>.
+     */
+    @Test
+    public void deserializeLeafListTest() {
+        final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
+                .create(schemaContext, "/deserializer-test:leaf-list-0=true");
+
+        assertEquals("Result does not contains expected number of path arguments", 2, Iterables.size(result));
+
+        final Iterator<YangInstanceIdentifier.PathArgument> iterator = result.iterator();
+        final QName leafList = QName.create("deserializer:test", "2016-06-06", "leaf-list-0");
+
+        assertEquals("Not expected path argument",
+                new YangInstanceIdentifier.NodeIdentifier(leafList),
+                iterator.next());
+        assertEquals("Not expected path argument",
+                new YangInstanceIdentifier.NodeWithValue(leafList, true).toString(),
+                iterator.next().toString());
+    }
+
+    /**
+     * Negative test when supplied <code>SchemaContext</code> is null. Test is expected to fail with
+     * <code>NullPointerException</code>.
+     */
+    @Test
+    public void deserializeNullSchemaContextNegativeTest() {
+        thrown.expect(NullPointerException.class);
+        YangInstanceIdentifierDeserializer.create(null, "/deserializer-test:contA");
+    }
+
+    /**
+     * Negative test when supplied <code>String</code> data to deserialize is null. Test is expected to fail with
+     * <code>NullPointerException</code>.
+     */
+    @Test
+    public void nullDataNegativeNegativeTest() {
+        thrown.expect(NullPointerException.class);
+        YangInstanceIdentifierDeserializer.create(schemaContext, null);
+    }
+
+    /**
+     * Negative test when empty <code>String</code> is supplied as an input. Test is expected to fail with
+     * <code>IllegalArgumentException</code>.
+     */
+    @Test
+    public void deserializeEmptyDataNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        YangInstanceIdentifierDeserializer.create(schemaContext, "");
+    }
+
+    /**
+     * Negative test when identifier is not followed by slash or equals. Test is expected to fail with
+     * <code>IllegalArgumentException</code>.
+     */
+    @Test
+    public void deserializeBadCharMissingSlashOrEqualNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:cont*leaf-A");
+    }
+
+    /**
+     * Negative test of validating identifier when identifier does not start with slash.
+     * <code>IllegalArgumentException</code> is expected.
+     */
+    @Test
+    public void deserializeNoBeginningSlashNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        YangInstanceIdentifierDeserializer.create(schemaContext, "deserializer-test:contA");
+    }
+
+    /**
+     * Positive test of validating identifier when identifier contains slash only. Deserialization should return
+     * empty result.
+     */
+    @Test
+    public void validArgOnlySlashTest() {
+        final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
+                .create(schemaContext, "/");
+        assertTrue("Result does not contains expected number of path arguments", Iterables.isEmpty(result));
+    }
+
+    /**
+     * Negative test of validating identifier when there is a slash after container without next identifier. Test
+     * is expected to fail with <code>IllegalArgumentException</code>.
+     */
+    @Test
+    public void validArgIdentifierContainerEndsWithSlashNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:contA/");
+    }
+
+    /**
+     * Negative test of validating identifier when there is a slash after list key values without next identifier. Test
+     * is expected to fail with <code>IllegalArgumentException</code>.
+     */
+    @Test
+    public void validArgIdentifierListEndsWithSlashLNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:list-one-key=value/");
+    }
+
+    /**
+     * Negative test of creating <code>QName</code> when identifier is empty (example: '//'). Test is expected to fail
+     * with <code>IllegalArgumentException</code>.
+     */
+    @Test
+    public void prepareQnameEmptyIdentifierNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        YangInstanceIdentifierDeserializer.create(schemaContext, "//");
+    }
+
+    /**
+     * Negative test of creating <code>QName</code> when two identifiers are separated by two slashes. Test is
+     * expected to fail with <code>IllegalArgumentException</code>.
+     */
+    @Test
+    public void prepareQnameTwoSlashesNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:contA//leaf-A");
+    }
+
+    /**
+     * Negative test of creating <code>QName</code> when in identifier there is another sign than colon or equals.
+     * Test is expected to fail with <code>IllegalArgumentException</code>.
+     */
+    @Test
+    public void prepareQnameBuildPathNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test*contA");
+    }
+
+    /**
+     * Negative test of creating <code>QName</code> when it is not possible to find module for specified prefix. Test is
+     * expected to fail with <code>IllegalArgumentException</code>.
+     */
+    @Test
+    public void prepareQnameNotExistingPrefixNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        YangInstanceIdentifierDeserializer.create(schemaContext, "/not-existing:contA");
+    }
+
+    /**
+     * Negative test of creating <code>QName</code> when after prefix and colon there is not parsable identifier as
+     * local name. Test is expected to fail with <code>IllegalArgumentException</code>.
+     */
+    @Test
+    public void prepareQnameNotValidPrefixAndLocalNameNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:*not-parsable-identifier");
+    }
+
+    /**
+     * Negative test of creating <code>QName</code> when data ends after prefix and colon. Test is expected to fail
+     * with <code>StringIndexOutOfBoundsException</code>.
+     */
+    @Test
+    public void prepareQnameErrorParsingNegativeTest() {
+        thrown.expect(StringIndexOutOfBoundsException.class);
+        YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:");
+    }
+
+    /**
+     * Negative test of creating <code>QName</code> when after identifier and colon there is node name of unknown
+     * node in current container. Test is expected to fail with <code>RestconfDocumentedException</code> and error
+     * type, error tag and error status code are compared to expected values.
+     */
+    @Test
+    public void prepareQnameNotValidContainerNameNegativeTest() {
+        try {
+            YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:contA/leafB");
+            fail("Test should fail due to unknown child node in container");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Not expected error type",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Not expected error tag",
+                    RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+            assertEquals("Not expected error status code",
+                    404, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test of creating <code>QName</code> when after identifier and equals there is node name of unknown
+     * node in current list. Test is expected to fail with <code>RestconfDocumentedException</code> and error
+     * type, error tag and error status code are compared to expected values.
+     */
+    @Test
+    public void prepareQnameNotValidListNameNegativeTest() {
+        try {
+            YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:list-no-key/disabled=false");
+            fail("Test should fail due to unknown child node in list");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Not expected error type",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Not expected error tag",
+                    RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+            assertEquals("Not expected error status code",
+                    404, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test of getting next identifier when current node is keyed entry. Test is expected to
+     * fail with <code>IllegalArgumentException</code>.
+     */
+    @Test
+    public void prepareIdentifierNotKeyedEntryNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:list-one-key");
+    }
+
+    /**
+     * Negative test when there is a comma also after the last key. Test is expected to fail with
+     * <code>IllegalArgumentException</code>.
+     */
+    @Test
+    public void deserializeKeysEndsWithComaNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        YangInstanceIdentifierDeserializer.create( schemaContext,
+                "/deserializer-test:list-multiple-keys=value,100,false,");
+    }
+
+    /**
+     * Positive when not all keys of list are encoded. The missing keys should be considered to has empty
+     * <code>String</code> values. Also value of next leaf must not be considered to be missing key value.
+     */
+    @Test
+    public void notAllListKeysEncodedPositiveTest() {
+        final QName list = QName.create("deserializer:test", "2016-06-06", "list-multiple-keys");
+        final Map<QName, Object> values = new LinkedHashMap<>();
+        values.put(QName.create(list, "name"), ":foo");
+        values.put(QName.create(list, "number"), "");
+        values.put(QName.create(list, "enabled"), "");
+
+        final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer.create(
+                schemaContext, "/deserializer-test:list-multiple-keys=%3Afoo,,/string-value");
+
+        assertEquals("Result does not contains expected number of path arguments", 3, Iterables.size(result));
+
+        final Iterator<YangInstanceIdentifier.PathArgument> iterator = result.iterator();
+
+        // list
+        assertEquals("Not expected path argument",
+                YangInstanceIdentifier.NodeIdentifier.create(list),
+                iterator.next());
+        assertEquals("Not expected path argument",
+                new YangInstanceIdentifier.NodeIdentifierWithPredicates(list, values).toString(),
+                iterator.next().toString());
+
+        // leaf
+        assertEquals("Not expected path argument",
+                new YangInstanceIdentifier.NodeIdentifier(
+                        QName.create("deserializer:test", "2016-06-06", "string-value")),
+                iterator.next());
+    }
+
+    /**
+     * Negative test when not all keys of list are encoded and it is not possible to consider missing keys to be empty.
+     * Test is expected to fail with <code>RestconfDocumentedException</code> and error type, error tag and error
+     * status code are compared to expected values.
+     */
+    @Test
+    public void notAllListKeysEncodedNegativeTest() {
+        try {
+            YangInstanceIdentifierDeserializer.create(
+                    schemaContext, "/deserializer-test:list-multiple-keys=%3Afoo/string-value");
+            fail("Test should fail due to missing list key values");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Not expected error type",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Not expected error tag",
+                    RestconfError.ErrorTag.MISSING_ATTRIBUTE, e.getErrors().get(0).getErrorTag());
+            assertEquals("Not expected error status code",
+                    400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test of preparing node with predicates when it is not possible to get <code>DataSchemaNode</code>.
+     * Test is expected to fail with <code>NullPointerException</code>.
+     */
+    @Ignore
+    @Test
+    public void prepareNodeWithPredicatesNegativeTest() {}
+
+    /**
+     * Test URI with list where key value starts with, ends with or contains percent encoded characters.The encoded
+     * value should be complete also with not percent-encoded parts.
+     */
+    @Test
+    public void percentEncodedKeyEndsWithNoPercentEncodedChars() {
+        final String URI = "/deserializer-test:list-multiple-keys=%3Afoo,bar%3A,foo%3Abar";
+        final YangInstanceIdentifier result = YangInstanceIdentifier.create(
+                YangInstanceIdentifierDeserializer.create(schemaContext, URI));
+
+        final Iterator<Map.Entry<QName, Object>> resultListKeys = ((YangInstanceIdentifier.NodeIdentifierWithPredicates)
+                result.getLastPathArgument()).getKeyValues().entrySet().iterator();
+
+        assertEquals(":foo", resultListKeys.next().getValue());
+        assertEquals("bar:", resultListKeys.next().getValue());
+        assertEquals("foo:bar", resultListKeys.next().getValue());
+    }
+
+    /**
+     * Positive test when all keys of list can be considered to be empty <code>String</code>.
+     */
+    @Test
+    public void deserializeAllKeysEmptyTest() {
+        final QName list = QName.create("deserializer:test", "2016-06-06", "list-multiple-keys");
+        final Map<QName, Object> values = new LinkedHashMap<>();
+        values.put(QName.create(list, "name"), "");
+        values.put(QName.create(list, "number"), "");
+        values.put(QName.create(list, "enabled"), "");
+
+        final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer
+                .create(schemaContext, "/deserializer-test:list-multiple-keys=,,");
+
+        assertEquals("Result does not contains expected number of path arguments", 2, Iterables.size(result));
+
+        final Iterator<YangInstanceIdentifier.PathArgument> iterator = result.iterator();
+
+        assertEquals("Not expected path argument",
+                YangInstanceIdentifier.NodeIdentifier.create(list),
+                iterator.next());
+        assertEquals("Not expected path argument",
+                new YangInstanceIdentifier.NodeIdentifierWithPredicates(list, values).toString(),
+                iterator.next().toString());
+    }
+
+    /**
+     * Negative test of deserialization when for leaf list there is no specified instance value.
+     * <code>RestconfDocumentedException</code> is expected and error type, error tag and error status code are
+     * compared to expected values.
+     */
+    @Test
+    public void leafListMissingKeyNegativeTest() {
+        try {
+            YangInstanceIdentifierDeserializer.create(schemaContext, "/deserializer-test:leaf-list-0=");
+            fail("Test should fail due to missing instance value");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Not expected error type",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Not expected error tag",
+                    RestconfError.ErrorTag.MISSING_ATTRIBUTE, e.getErrors().get(0).getErrorTag());
+            assertEquals("Not expected error status code",
+                    400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Positive test of deserialization when parts of input URI <code>String</code> are defined in another module.
+     */
+    @Test
+    public void deserializePartInOtherModuleTest() {
+        final Iterable<YangInstanceIdentifier.PathArgument> result = YangInstanceIdentifierDeserializer.create(
+                schemaContext, "/deserializer-test-included:augmented-list=100/augmented-leaf");
+
+        assertEquals("Result does not contains expected number of path arguments", 4, Iterables.size(result));
+
+        final Iterator<YangInstanceIdentifier.PathArgument> iterator = result.iterator();
+        final QName list = QName.create("deserializer:test:included", "2016-06-06", "augmented-list");
+        final QName child = QName.create("deserializer:test", "2016-06-06", "augmented-leaf");
+
+        // list
+        assertEquals("Not expected path argument",
+                YangInstanceIdentifier.NodeIdentifier.create(list),
+                iterator.next());
+
+        assertEquals("Not expected path argument",
+                new YangInstanceIdentifier.NodeIdentifierWithPredicates(list, QName.create(list, "list-key"), 100)
+                        .toString(),
+                iterator.next()
+                        .toString());
+
+        // augmented leaf
+        assertEquals("Not expected path argument",
+                new YangInstanceIdentifier.AugmentationIdentifier(Sets.newHashSet(child)),
+                iterator.next());
+
+        assertEquals("Not expected path argument",
+                YangInstanceIdentifier.NodeIdentifier.create(child),
+                iterator.next());
+    }
+}
index 6a774286de7907cdace721110b94412cdcfb9fbb..965ce3499528cdbfb90ea7904118d77dba549609 100644 (file)
  * 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.restconf.parser.builder;
 
-import org.junit.Assert;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import java.util.LinkedHashMap;
+import java.util.Map;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils;
-import org.opendaylight.restconf.parser.IdentifierCodec;
+import org.opendaylight.restconf.utils.parser.builder.ParserBuilderConstants;
+import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 
+/**
+ * Unit tests for {@link YangInstanceIdentifierSerializer}
+ */
 public class YangInstanceIdentifierSerializerTest {
 
+    @Rule
+    public ExpectedException thrown = ExpectedException.none();
+
+    // schema context with test modules
     private SchemaContext schemaContext;
-    private final String data = "/list-test:top/list1=%2C%27" + '"' + "%3A" + '"' + "%20%2F,,foo/list2=a,b/result=x";
 
     @Before
     public void init() throws Exception {
-        this.schemaContext = TestRestconfUtils.loadSchemaContext("/restconf/parser");
+        schemaContext = TestRestconfUtils.loadSchemaContext("/restconf/parser/serializer");
+    }
+
+    /**
+     * Positive test of serialization of <code>YangInstanceIdentifier</code> containing container node to
+     * <code>String</code>. Returned <code>String</code> is compared to have expected value.
+     */
+    @Test
+    public void serializeContainerTest() {
+        final YangInstanceIdentifier data = YangInstanceIdentifier.builder()
+                .node(QName.create("serializer:test", "2016-06-06", "contA"))
+                .build();
+
+        final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
+        assertEquals("Serialization not successful",
+                "/serializer-test:contA", result);
     }
 
+    /**
+     * Positive test of serialization of <code>YangInstanceIdentifier</code> containing container with leaf node to
+     * <code>String</code>. Returned <code>String</code> is compared to have expected value.
+     */
     @Test
-    public void parserTest() {
-        final YangInstanceIdentifier dataYangII = YangInstanceIdentifier
-                .create(YangInstanceIdentifierDeserializer.create(this.schemaContext, this.data));
+    public void serializeContainerWithLeafTest() {
+        final YangInstanceIdentifier data = YangInstanceIdentifier.builder()
+                .node(QName.create("serializer:test", "2016-06-06", "contA"))
+                .node(QName.create("serializer:test", "2016-06-06", "leaf-A"))
+                .build();
+
+        final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
+        assertEquals("Serialization not successful", "/serializer-test:contA/leaf-A", result);
+    }
+
+    /**
+     * Positive test of serialization of <code>YangInstanceIdentifier</code> containing container with list with leaf
+     * list node to <code>String</code>. Returned <code>String</code> is compared to have expected value.
+     */
+    @Test
+    public void serializeContainerWithListWithLeafListTest() {
+        final QName list = QName.create("serializer:test", "2016-06-06", "list-A");
+        final QName leafList = QName.create("serializer:test", "2016-06-06", "leaf-list-AA");
+
+        final YangInstanceIdentifier data = YangInstanceIdentifier.builder()
+                .node(QName.create("serializer:test", "2016-06-06", "contA"))
+                .node(list)
+                .node(new YangInstanceIdentifier.NodeIdentifierWithPredicates(
+                        list, QName.create(list, "list-key"), 100))
+                .node(leafList)
+                .node(new NodeWithValue(leafList, "instance"))
+                .build();
+
+        final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
+        assertEquals("Serialization not successful",
+                "/serializer-test:contA/list-A=100/leaf-list-AA=instance",
+                result);
+    }
+
+    /**
+     * Positive test of serialization of <code>YangInstanceIdentifier</code> to <code>String</code> when serialized
+     * <code>YangInstanceIdentifier</code> contains list with no keys. Returned <code>String</code> is compared to have
+     * expected value.
+     */
+    @Test
+    public void serializeListWithNoKeysTest() {
+        final YangInstanceIdentifier data = YangInstanceIdentifier.builder()
+                .node(QName.create("serializer:test", "2016-06-06", "list-no-key"))
+                .nodeWithKey(QName.create("serializer:test", "2016-06-06", "list-no-key"), Maps.newHashMap())
+                .build();
+
+        final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
+        assertEquals("Serialization not successful", "/serializer-test:list-no-key", result);
+    }
+
+    /**
+     * Positive test of serialization of <code>YangInstanceIdentifier</code> to <code>String</code> when serialized
+     * <code>YangInstanceIdentifier</code> contains list with one key. Returned <code>String</code> is compared to have
+     * expected value.
+     */
+    @Test
+    public void serializeListWithOneKeyTest() {
+        final YangInstanceIdentifier data = YangInstanceIdentifier.builder()
+                .node(QName.create("serializer:test", "2016-06-06", "list-one-key"))
+                .nodeWithKey(QName.create("serializer:test", "2016-06-06", "list-one-key"),
+                        QName.create("serializer:test", "2016-06-06", "list-one-key"), "value")
+                .build();
+
+        final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
+        assertEquals("Serialization not successful", "/serializer-test:list-one-key=value", result);
+    }
+
+    /**
+     * Positive test of serialization of <code>YangInstanceIdentifier</code> to <code>String</code> when serialized
+     * <code>YangInstanceIdentifier</code> contains list with multiple keys. Returned <code>String</code> is compared
+     * to have expected value.
+     */
+    @Test
+    public void serializeListWithMultipleKeysTest() {
+        final QName list = QName.create("serializer:test", "2016-06-06", "list-multiple-keys");
+        final Map<QName, Object> values = new LinkedHashMap<>();
+        values.put(QName.create(list, "name"), "value-1");
+        values.put(QName.create(list, "number"), "2");
+        values.put(QName.create(list, "enabled"), "true");
+
+        final YangInstanceIdentifier data = YangInstanceIdentifier.builder()
+                .node(list).nodeWithKey(list, values).build();
+
+        final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
+        assertEquals("Serialization not successful", "/serializer-test:list-multiple-keys=value-1,2,true", result);
+    }
+
+    /**
+     * Positive test of serialization of <code>YangInstanceIdentifier</code> to <code>String</code> when serialized
+     * <code>YangInstanceIdentifier</code> contains leaf node. Returned <code>String</code> is compared to have
+     * expected value.
+     */
+    @Test
+    public void serializeLeafTest() {
+        final YangInstanceIdentifier data = YangInstanceIdentifier.builder()
+                .node(QName.create("serializer:test", "2016-06-06", "leaf-0"))
+                .build();
+
+        final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
+        assertEquals("Serialization not successful", "/serializer-test:leaf-0", result);
+    }
+
+    /**
+     * Positive test of serialization of <code>YangInstanceIdentifier</code> to <code>String</code> when serialized
+     * <code>YangInstanceIdentifier</code> contains leaf list node. Returned <code>String</code> is compared to have
+     * expected value.
+     */
+    @Test
+    public void serializeLeafListTest() {
+        final YangInstanceIdentifier data = YangInstanceIdentifier.builder()
+                .node(QName.create("serializer:test", "2016-06-06", "leaf-list-0"))
+                .node(new NodeWithValue(QName.create("serializer:test", "2016-06-06", "leaf-list-0"), "instance"))
+                .build();
+
+        final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
+        assertEquals("Serialization not successful", "/serializer-test:leaf-list-0=instance", result);
+    }
+
+    /**
+     * Negative test of serialization <code>YangInstanceIdentifier</code> to <code>String</code> when
+     * <code>SchemaContext</code> is <code>null</code>. Test is expected to fail with
+     * <code>NullPointerException</code>.
+     */
+    @Test
+    public void serializeNullSchemaContextNegativeTest() {
+        thrown.expect(NullPointerException.class);
+        YangInstanceIdentifierSerializer.create(null, YangInstanceIdentifier.EMPTY);
+    }
+
+    /**
+     * Negative test of serialization <code>YangInstanceIdentifier</code> to <code>String</code> when supplied
+     * <code>YangInstanceIdentifier</code> is <code>null</code>. Test is expected to fail with
+     * <code>NullPointerException</code>.
+     */
+    @Test
+    public void serializeNullDataNegativeTest() {
+        thrown.expect(NullPointerException.class);
+        YangInstanceIdentifierSerializer.create(schemaContext, null);
+    }
+
+    /**
+     * Test of serialization <code>YangInstanceIdentifier</code> to <code>String</code> when supplied
+     * <code>YangInstanceIdentifier</code> is <code>YangInstanceIdentifier.EMPTY</code>.
+     * Single slash is expected as a return value.
+     */
+    @Test
+    public void serializeEmptyDataTest() {
+        final String result = YangInstanceIdentifierSerializer.create(schemaContext, YangInstanceIdentifier.EMPTY);
+        assertEquals("Empty identifier is expected", "/", result);
+    }
+
+    /**
+     * Negative test when it is not possible to find child node of current node. Test is expected to fail with
+     * <code>IllegalArgumentException</code> and error message is compared to expected error message.
+     */
+    @Test
+    public void serializeChildNodeNotFoundNegativeTest() {
+        final YangInstanceIdentifier data = YangInstanceIdentifier.builder()
+                .node(QName.create("serializer:test", "2016-06-06", "contA"))
+                .node(QName.create("serializer:test", "2016-06-06", "not-existing-leaf"))
+                .build();
+
+        thrown.expect(IllegalArgumentException.class);
+        YangInstanceIdentifierSerializer.create(schemaContext, data);
+    }
+
+    /**
+     * Test to verify if all reserved characters according to rfc3986 are considered by serializer implementation to
+     * be percent encoded.
+     */
+    @Test
+    public void verifyReservedCharactersTest() {
+        final char[] genDelims = { ':', '/', '?', '#', '[', ']', '@' };
+        final char[] subDelims = { '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=' };
+
+        for (final char c : genDelims) {
+            assertTrue("Current character is reserved and should be percent encoded",
+                    ParserBuilderConstants.Serializer.PERCENT_ENCODE_CHARS.matches(c));
+        }
+
+        for (final char c : subDelims) {
+            assertTrue("Current character is reserved and should be percent encoded",
+                    ParserBuilderConstants.Serializer.PERCENT_ENCODE_CHARS.matches(c));
+        }
+    }
+
+    /**
+     * Test if URIs with percent encoded characters are all correctly serialized.
+     */
+    @Test
+    public void serializePercentEncodingTest() {
+        final String value = "foo" + ":foo bar/" + "foo,bar/" + "'bar'";
+        final String encoded = "foo%3Afoo%20bar%2Ffoo%2Cbar%2F%27bar%27";
+
+        final YangInstanceIdentifier data = YangInstanceIdentifier.builder()
+                .node(QName.create("serializer:test", "2016-06-06", "list-one-key"))
+                .nodeWithKey(QName.create("serializer:test", "2016-06-06", "list-one-key"),
+                        QName.create("serializer:test", "2016-06-06", "list-one-key"), value)
+                .build();
+
+        final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
+        assertEquals("Serialization not successful", "/serializer-test:list-one-key=" + encoded, result);
+    }
+
+    /**
+     * Test if URIs with no percent encoded characters are correctly serialized. Input should be untouched.
+     */
+    @Test
+    public void serializeNoPercentEncodingTest() {
+        final String value = "foo\"b\"bar";
+
+        final YangInstanceIdentifier data = YangInstanceIdentifier.builder()
+                .node(QName.create("serializer:test", "2016-06-06", "list-one-key"))
+                .nodeWithKey(QName.create("serializer:test", "2016-06-06", "list-one-key"),
+                        QName.create("serializer:test", "2016-06-06", "list-one-key"), value)
+                .build();
+
+        final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
+        assertEquals("Serialization not successful", "/serializer-test:list-one-key=" + value, result);
+    }
+
+    /**
+     * Test of serialization when nodes in input <code>YangInstanceIdentifier</code> are defined in two different
+     * modules by using augmentation.
+     */
+    @Test
+    public void serializeIncludedNodesTest() {
+        final QName list = QName.create("serializer:test:included", "2016-06-06", "augmented-list");
+        final QName child = QName.create("serializer:test", "2016-06-06", "augmented-leaf");
+
+        final YangInstanceIdentifier data = YangInstanceIdentifier.builder()
+                .node(list)
+                .node(new YangInstanceIdentifier.NodeIdentifierWithPredicates(
+                        list, QName.create(list, "list-key"), 100))
+                .node(new YangInstanceIdentifier.AugmentationIdentifier(Sets.newHashSet(child)))
+                .node(child)
+                .build();
+
+        final String result = YangInstanceIdentifierSerializer.create(schemaContext, data);
+
+        assertEquals("Serialization not successful",
+                "/serializer-test-included:augmented-list=100/serializer-test:augmented-leaf", result);
+    }
+
+    /**
+     * Test of serialization when nodes in input <code>YangInstanceIdentifier</code> are defined in two different
+     * modules by using augmentation. Augmented node in data supplied for serialization has wrong namespace.
+     * <code>IllegalArgumentException</code> is expected because augmented node is defined in other module than its
+     * parent and will not be found.
+     */
+    @Test
+    public void serializeIncludedNodesSerializationTest() {
+        final QName list = QName.create("serializer:test:included", "2016-06-06", "augmented-list");
+        // child should has different namespace
+        final QName child = QName.create("serializer:test:included", "2016-06-06", "augmented-leaf");
+
+        final YangInstanceIdentifier data = YangInstanceIdentifier.builder()
+                .node(list)
+                .node(new YangInstanceIdentifier.NodeIdentifierWithPredicates(
+                        list, QName.create(list, "list-key"), 100))
+                .node(new YangInstanceIdentifier.AugmentationIdentifier(Sets.newHashSet(child)))
+                .node(child)
+                .build();
 
-        final String serializedDataYangII = IdentifierCodec.serialize(dataYangII, this.schemaContext);
-        Assert.assertEquals(this.data, serializedDataYangII);
+        thrown.expect(IllegalArgumentException.class);
+        YangInstanceIdentifierSerializer.create(schemaContext, data);
     }
 }
diff --git a/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/utils/parser/ParserIdentifierTest.java b/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/utils/parser/ParserIdentifierTest.java
new file mode 100644 (file)
index 0000000..7793075
--- /dev/null
@@ -0,0 +1,635 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. 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.restconf.utils.parser;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.when;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableClassToInstanceMap;
+import com.google.common.collect.Maps;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
+import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
+import org.opendaylight.controller.md.sal.dom.broker.impl.mount.DOMMountPointServiceImpl;
+import org.opendaylight.controller.md.sal.dom.broker.spi.mount.SimpleDOMMountPoint;
+import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils;
+import org.opendaylight.netconf.md.sal.rest.schema.SchemaExportContext;
+import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
+import org.opendaylight.restconf.utils.RestconfConstants;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+/**
+ * Unit tests for {@link ParserIdentifier}
+ */
+public class ParserIdentifierTest {
+    // mount point identifier + expected result
+    private static final String MOUNT_POINT_IDENT =
+            "/mount-point:mount-container/point-number" + "/" + RestconfConstants.MOUNT;
+
+    private static final String MOUNT_POINT_IDENT_RESULT =
+            "/(mount:point?revision=2016-06-02)mount-container/point-number";
+
+    // invalid mount point identifier
+    private static final String INVALID_MOUNT_POINT_IDENT =
+            "/mount-point:point-number" + "/" + RestconfConstants.MOUNT;
+
+    // test identifier + expected result
+    private static final String TEST_IDENT =
+            "/parser-identifier:cont1/cont2/listTest/list-in-grouping=name/leaf-A.B";
+
+    private static final String TEST_IDENT_RESULT =
+            "/(parser:identifier?revision=2016-06-02)cont1/cont2/listTest/listTest/list-in-grouping/"
+            + "list-in-grouping[{(parser:identifier?revision=2016-06-02)name=name}]/leaf-A.B";
+
+    // test identifier with nodes defined in other modules using augmentation + expected result
+    private static final String TEST_IDENT_OTHERS =
+            "/parser-identifier-included:list-1=name,2016-06-02/parser-identifier:augment-leaf";
+
+    private static final String TEST_IDENT_OTHERS_RESULT =
+            "/(parser:identifier:included?revision=2016-06-02)list-1/list-1"
+            + "[{(parser:identifier:included?revision=2016-06-02)name=name, "
+            + "(parser:identifier:included?revision=2016-06-02)revision=2016-06-02}]"
+            + "/AugmentationIdentifier{childNames=[(parser:identifier?revision=2016-06-02)augment-leaf]}/"
+            + "(parser:identifier?revision=2016-06-02)augment-leaf";
+
+    // invalid test identifier
+    private static final String INVALID_TEST_IDENT =
+            "/parser-identifier:cont2/listTest/list-in-grouping=name/leaf-A.B";
+
+    // schema context with test modules
+    private SchemaContext schemaContext;
+
+    private static final String TEST_MODULE_NAME = "test-module";
+    private static final String TEST_MODULE_REVISION = "2016-06-02";
+    private static final String TEST_MODULE_NAMESPACE = "test:module";
+    private static final String MOUNT_POINT_IDENT_WITHOUT_SLASH = MOUNT_POINT_IDENT.replaceFirst("/", "");
+
+    // mount point and mount point service
+    private DOMMountPoint mountPoint;
+    private DOMMountPointService mountPointService;
+
+    // mock mount point and mount point service
+    @Mock DOMMountPoint mockMountPoint;
+    @Mock DOMMountPointService mockMountPointService;
+
+    @Rule
+    public final ExpectedException thrown = ExpectedException.none();
+
+    @Before
+    public void setup() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        schemaContext = TestRestconfUtils.loadSchemaContext("/parser-identifier");
+
+        // create and register mount point
+        mountPoint = SimpleDOMMountPoint.create(
+                YangInstanceIdentifier.builder()
+                        .node(QName.create("mount:point", "2016-06-02", "mount-container"))
+                        .node(QName.create("mount:point", "2016-06-02", "point-number"))
+                        .build(),
+                ImmutableClassToInstanceMap.copyOf(Maps.newHashMap()),
+                schemaContext
+        );
+
+        mountPointService = new DOMMountPointServiceImpl();
+        ((DOMMountPointServiceImpl) mountPointService).registerMountPoint(mountPoint);
+
+        // register mount point with null schema context
+        when(mockMountPoint.getSchemaContext()).thenReturn(null);
+        when(mockMountPointService.getMountPoint(YangInstanceIdentifier.EMPTY)).thenReturn(Optional.of(mockMountPoint));
+    }
+
+    /**
+     * {@link ParserIdentifier#toInstanceIdentifier(String, SchemaContext)} tests
+     */
+
+    /**
+     * Positive test of creating <code>InstanceIdentifierContext</code> from identifier when all nodes are defined
+     * in one module.
+     */
+    @Test
+    public void toInstanceIdentifierTest() {
+        final InstanceIdentifierContext<?> context = ParserIdentifier.toInstanceIdentifier(
+                TEST_IDENT, schemaContext);
+
+        assertEquals("Returned not expected identifier",
+                TEST_IDENT_RESULT, context .getInstanceIdentifier().toString());
+    }
+
+    /**
+     * Positive test of creating <code>InstanceIdentifierContext</code> from identifier when nodes are defined in
+     * multiple modules.
+     */
+    @Test
+    public void toInstanceIdentifierOtherModulesTest() {
+        final InstanceIdentifierContext<?> context = ParserIdentifier.toInstanceIdentifier(
+                TEST_IDENT_OTHERS, schemaContext);
+
+        assertEquals("Returned not expected identifier",
+                TEST_IDENT_OTHERS_RESULT, context.getInstanceIdentifier().toString());
+    }
+
+    /**
+     * Positive test of creating <code>InstanceIdentifierContext</code> from identifier containing
+     * {@link RestconfConstants#MOUNT}.
+     */
+    @Test
+    public void toInstanceIdentifierMountPointTest() {
+        final InstanceIdentifierContext<?> context = ParserIdentifier.toInstanceIdentifier(
+                MOUNT_POINT_IDENT, schemaContext);
+
+        assertEquals("Returned not expected identifier",
+                MOUNT_POINT_IDENT_RESULT, context.getInstanceIdentifier().toString());
+    }
+
+    /**
+     * Negative test of creating <code>InstanceIdentifierContext</code> when identifier is <code>null</code>. Test
+     * fails expecting <code>NullPointerException</code>.
+     */
+    @Test
+    public void toInstanceIdentifierNullIdentifierNegativeTest() {
+        thrown.expect(NullPointerException.class);
+        ParserIdentifier.toInstanceIdentifier(null, schemaContext);
+    }
+
+    /**
+     * Negative test of creating <code>InstanceIdentifierContext</code> when <code>SchemaContext</code> is
+     * <code>null</code>. Test fails expecting <code>NullPointerException</code>.
+     */
+    @Test
+    public void toInstanceIdentifierNullSchemaContextNegativeTest() {
+        thrown.expect(NullPointerException.class);
+        ParserIdentifier.toInstanceIdentifier(TEST_IDENT, null);
+    }
+
+    /**
+     * Api path can contains single slash. <code>YangInstanceIdentifier.EMPTY</code> is expected to be returned.
+     */
+    @Test
+    public void toInstanceIdentifierSlashIdentifierTest() {
+        final InstanceIdentifierContext<?> context = ParserIdentifier.toInstanceIdentifier("/", schemaContext);
+        assertEquals("Returned not expected identifier",
+                YangInstanceIdentifier.EMPTY, context.getInstanceIdentifier());
+    }
+
+    /**
+     * Api path can contains single slash. <code>YangInstanceIdentifier.EMPTY</code> is expected to be returned.
+     * Test when identifier contains {@link RestconfConstants#MOUNT}.
+     */
+    @Test
+    public void toInstanceIdentifierSlashIdentifierMountPointTest() {
+        final InstanceIdentifierContext<?> context = ParserIdentifier.toInstanceIdentifier(
+                "/" + "/" + RestconfConstants.MOUNT, schemaContext);
+        assertEquals("Returned not expected identifier",
+                YangInstanceIdentifier.EMPTY, context.getInstanceIdentifier());
+    }
+
+    /**
+     * Negative test of creating <code>InstanceIdentifierContext</code> with empty identifier.
+     * <code>IllegalArgumentException</code> is expected.
+     */
+    @Test
+    public void toInstanceIdentifierEmptyIdentifierNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        ParserIdentifier.toInstanceIdentifier("", schemaContext);
+    }
+
+    /**
+     * Negative test of creating <code>InstanceIdentifierContext</code> from identifier containing
+     * {@link RestconfConstants#MOUNT} when identifier part is empty. <code>IllegalArgumentException</code> is expected.
+     */
+    @Test
+    public void toInstanceIdentifierMountPointEmptyIdentifierNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        ParserIdentifier.toInstanceIdentifier("/" + RestconfConstants.MOUNT, schemaContext);
+    }
+
+    /**
+     * Negative test with invalid test identifier. Test should fail with <code>IllegalArgumentException</code>.
+     */
+    @Test
+    public void toInstanceIdentifierInvalidIdentifierNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        ParserIdentifier.toInstanceIdentifier(INVALID_TEST_IDENT, schemaContext);
+    }
+
+    /**
+     * Negative test when identifier contains {@link RestconfConstants#MOUNT} but identifier part is not valid. Test
+     * should fail with <code>IllegalArgumentException</code>.
+     */
+    @Test
+    public void toInstanceIdentifierMountPointInvalidIdentifierNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        ParserIdentifier.toInstanceIdentifier(INVALID_MOUNT_POINT_IDENT, schemaContext);
+    }
+
+    /**
+     * {@link ParserIdentifier#makeQNameFromIdentifier(String)} tests
+     */
+
+    /**
+     * Positive test of making <code>QName</code> from identifier and compare values from returned <code>QName</code>
+     * to expected values.
+     */
+    @Test
+    public void makeQNameFromIdentifierTest() {
+        final QName qName = ParserIdentifier.makeQNameFromIdentifier(TEST_MODULE_NAME + "/" + TEST_MODULE_REVISION);
+
+        assertNotNull("QName should be created", qName);
+        assertEquals("Returned not expected module name",
+                TEST_MODULE_NAME, qName.getLocalName());
+        assertEquals("Returned not expected module revision",
+                TEST_MODULE_REVISION, qName.getFormattedRevision());
+    }
+
+    /**
+     * Negative test when supplied identifier is in invalid format and then revision is not parsable.
+     * <code>RestconfDocumentedException</code> is expected and error type, error tag and error status code are
+     * compared to expected values.
+     */
+    @Test
+    public void makeQNameFromIdentifierInvalidIdentifierNegativeTest() {
+        try {
+            ParserIdentifier.makeQNameFromIdentifier(TEST_MODULE_REVISION + "/" + TEST_MODULE_NAME);
+            fail("Test should fail due to invalid identifier format");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Not expected error type",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Not expected error tag",
+                    RestconfError.ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+            assertEquals("Not expected error status code",
+                    400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test when supplied identifier is too short (contains only module name).
+     * <code>RestconfDocumentedException</code> is expected and error type, error tag and error status code are
+     * compared to expected values.
+     */
+    @Test
+    public void makeQNameFromIdentifierTooShortIdentifierNegativeTest() {
+        try {
+            ParserIdentifier.makeQNameFromIdentifier(TEST_MODULE_NAME);
+            fail("Test should fail due to too short identifier format");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Not expected error type",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Not expected error tag",
+                    RestconfError.ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+            assertEquals("Not expected error status code",
+                    400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Positive test of making <code>QName</code> from identifier for module behind mount point. Value from returned
+     * <code>QName</code> are compared to expected values.
+     */
+    @Test
+    public void makeQNameFromIdentifierMountTest() {
+        final QName qName = ParserIdentifier.makeQNameFromIdentifier(
+                MOUNT_POINT_IDENT
+                + "/"
+                + TEST_MODULE_NAME
+                + "/"
+                + TEST_MODULE_REVISION);
+
+        assertNotNull("QName should be created", qName);
+        assertEquals("Returned not expected module name",
+                TEST_MODULE_NAME, qName.getLocalName());
+        assertEquals("Returned not expected module revision",
+                TEST_MODULE_REVISION, qName.getFormattedRevision());
+    }
+
+    /**
+     * Negative test when supplied identifier for module behind mount point is in invalid format and then revision is
+     * not parsable. <code>RestconfDocumentedException</code> is expected and error type, error tag and error status
+     * code are compared to expected values.
+     */
+    @Test
+    public void makeQNameFromIdentifierMountPointInvalidIdentifierNegativeTest() {
+        try {
+            ParserIdentifier.makeQNameFromIdentifier(
+                    MOUNT_POINT_IDENT
+                    + "/"
+                    + TEST_MODULE_REVISION
+                    + "/"
+                    + TEST_MODULE_NAME);
+
+            fail("Test should fail due to invalid identifier format");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Not expected error type",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Not expected error tag",
+                    RestconfError.ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+            assertEquals("Not expected error status code",
+                    400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test when supplied identifier for module behind mount point is too short (contains only module name).
+     * <code>RestconfDocumentedException</code> is expected and error type, error tag and error status code
+     * are compared to expected values.
+     */
+    @Test
+    public void makeQNameFromIdentifierMountPointTooShortIdentifierNegativeTest() {
+        try {
+            ParserIdentifier.makeQNameFromIdentifier(
+                    MOUNT_POINT_IDENT
+                    + "/"
+                    + TEST_MODULE_NAME);
+
+            fail("Test should fail due to too short identifier format");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Not expected error type",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Not expected error tag",
+                    RestconfError.ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+            assertEquals("Not expected error status code",
+                    400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test trying to make <code>QName</code> from <code>null</code> identifier. Test is expected to fail with
+     * <code>NullPointerException</code>.
+     */
+    @Test
+    public void makeQNameFromIdentifierNullIdentifierNegativeTest() {
+        thrown.expect(NullPointerException.class);
+        ParserIdentifier.makeQNameFromIdentifier(null);
+    }
+
+    /**
+     * Negative test trying to make <code>QName</code> from empty identifier. Test is expected to fail with
+     * <code>RestconfDocumentedException</code>. Error type, error tag and error status code is compared to expected
+     * values.
+     */
+    @Test
+    public void makeQNameFromIdentifierEmptyIdentifierNegativeTest() {
+        try {
+            ParserIdentifier.makeQNameFromIdentifier("");
+            fail("Test should fail due to empty identifier");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Not expected error type",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Not expected error tag",
+                    RestconfError.ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+            assertEquals("Not expected error status code",
+                    400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test with identifier containing double slash. Between // there is one empty string which will be
+     * incorrectly considered to be module revision. Test is expected to fail with
+     * <code>RestconfDocumentedException</code> and error type, error tag and error status code are compared to
+     * expected values.
+     */
+    @Test
+    public void makeQNameFromIdentifierDoubleSlashNegativeTest() {
+        try {
+            ParserIdentifier.makeQNameFromIdentifier(TEST_MODULE_NAME + "//" + TEST_MODULE_REVISION);
+            fail("Test should fail due to identifier containing double slash");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Not expected error type",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Not expected error tag",
+                    RestconfError.ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+            assertEquals("Not expected error status code",
+                    400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * {@link ParserIdentifier#toSchemaExportContextFromIdentifier(SchemaContext, String, DOMMountPointService)} tests
+     */
+
+    /**
+     * Positive test of getting <code>SchemaExportContext</code>. Expected module name, revision and namespace are
+     * verified.
+     */
+    @Test
+    public void toSchemaExportContextFromIdentifierTest() {
+        final SchemaExportContext exportContext = ParserIdentifier.
+                toSchemaExportContextFromIdentifier(schemaContext, TEST_MODULE_NAME + "/" + TEST_MODULE_REVISION, null);
+
+        assertNotNull("Export context should be parsed", exportContext);
+
+        final Module module = exportContext.getModule();
+        assertNotNull("Export context should contains test module", module);
+
+        assertEquals("Returned not expected module name",
+                TEST_MODULE_NAME, module.getName());
+        assertEquals("Returned not expected module revision",
+                TEST_MODULE_REVISION, SimpleDateFormatUtil.getRevisionFormat().format(module.getRevision()));
+        assertEquals("Returned not expected module namespace",
+                TEST_MODULE_NAMESPACE, module.getNamespace().toString());
+    }
+
+    /**
+     * Test of getting <code>SchemaExportContext</code> when desired module is not found.
+     * <code>SchemaExportContext</code> should be created but module should be set to <code>null</code>.
+     */
+    @Test
+    public void toSchemaExportContextFromIdentifierNotFoundTest() {
+        final SchemaExportContext exportContext = ParserIdentifier.toSchemaExportContextFromIdentifier(
+                schemaContext,
+                "not-existing-module" + "/" + "2016-01-01",
+                null);
+
+        assertNotNull("Export context should be parsed", exportContext);
+        assertNull("Not-existing module should be null", exportContext.getModule());
+    }
+
+    /**
+     * Negative test trying to get <code>SchemaExportContext</code> with invalid identifier. Test is expected to fail
+     * with <code>RestconfDocumentedException</code> error type, error tag and error status code are compared to
+     * expected values.
+     */
+    @Test
+    public void toSchemaExportContextFromIdentifierInvalidIdentifierNegativeTest() {
+        try {
+            ParserIdentifier.toSchemaExportContextFromIdentifier(
+                    schemaContext, TEST_MODULE_REVISION + "/" + TEST_MODULE_NAME, null);
+            fail("Test should fail due to invalid identifier supplied");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Not expected error type",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Not expected error tag",
+                    RestconfError.ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+            assertEquals("Not expected error status code",
+                    400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Positive test of getting <code>SchemaExportContext</code> for module behind mount point.
+     * Expected module name, revision and namespace are verified.
+     */
+    @Test
+    public void toSchemaExportContextFromIdentifierMountPointTest() {
+        final SchemaExportContext exportContext = ParserIdentifier.toSchemaExportContextFromIdentifier(
+                schemaContext,
+                MOUNT_POINT_IDENT_WITHOUT_SLASH + "/" + TEST_MODULE_NAME + "/" + TEST_MODULE_REVISION,
+                mountPointService);
+
+        final Module module = exportContext.getModule();
+        assertNotNull("Export context should contains test module", module);
+
+        assertEquals("Returned not expected module name",
+                TEST_MODULE_NAME, module.getName());
+        assertEquals("Returned not expected module revision",
+                TEST_MODULE_REVISION, SimpleDateFormatUtil.getRevisionFormat().format(module.getRevision()));
+        assertEquals("Returned not expected module namespace",
+                TEST_MODULE_NAMESPACE, module.getNamespace().toString());
+    }
+
+    /**
+     * Negative test of getting <code>SchemaExportContext</code> when desired module is not found behind mount point.
+     * <code>SchemaExportContext</code> should be still created but module should be set to <code>null</code>.
+     */
+    @Test
+    public void toSchemaExportContextFromIdentifierMountPointNotFoundTest() {
+        final SchemaExportContext exportContext = ParserIdentifier.toSchemaExportContextFromIdentifier(
+                schemaContext,
+                MOUNT_POINT_IDENT_WITHOUT_SLASH + "/" + "not-existing-module" + "/" + "2016-01-01",
+                mountPointService);
+
+        assertNotNull("Export context should be parsed", exportContext);
+        assertNull("Not-existing module should be null", exportContext.getModule());
+    }
+
+    /**
+     * Negative test trying to get <code>SchemaExportContext</code> behind mount point with invalid identifier. Test is
+     * expected to fail with <code>RestconfDocumentedException</code> error type, error tag and error status code are
+     * compared to expected values.
+     */
+    @Test
+    public void toSchemaExportContextFromIdentifierMountPointInvalidIdentifierNegativeTest() {
+        try {
+            ParserIdentifier.toSchemaExportContextFromIdentifier(
+                    schemaContext,
+                    MOUNT_POINT_IDENT_WITHOUT_SLASH + "/" + TEST_MODULE_REVISION + "/" + TEST_MODULE_NAME,
+                    mountPointService);
+
+            fail("Test should fail due to invalid identifier supplied");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Not expected error type",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Not expected error tag",
+                    RestconfError.ErrorTag.INVALID_VALUE, e.getErrors().get(0).getErrorTag());
+            assertEquals("Not expected error status code",
+                    400, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test of getting <code>SchemaExportContext</code> with identifier beginning with slash defining module
+     * behind mount point. Test is expected to fail with <code>IllegalArgumentException</code>.
+     */
+    @Test
+    public void toSchemaExportContextFromIdentifierMountPointBeginsWithSlashNegativeTest() {
+        thrown.expect(IllegalArgumentException.class);
+        ParserIdentifier.toSchemaExportContextFromIdentifier(
+                schemaContext,
+                MOUNT_POINT_IDENT + "/" + TEST_MODULE_NAME + "/" + TEST_MODULE_REVISION,
+                mountPointService);
+    }
+
+    /**
+     * Negative test of getting <code>SchemaExportContext</code> when supplied identifier is null.
+     * <code>NullPointerException</code> is expected. <code>DOMMountPointService</code> is not used.
+     */
+    @Test
+    public void toSchemaExportContextFromIdentifierNullIdentifierNegativeTest() {
+        thrown.expect(NullPointerException.class);
+        ParserIdentifier.toSchemaExportContextFromIdentifier(schemaContext, null, null);
+    }
+
+    /**
+     * Negative test of of getting <code>SchemaExportContext</code> when supplied <code>SchemaContext</code> is
+     * <code>null</code>. Test is expected to fail with <code>NullPointerException</code>.
+     */
+    @Test
+    public void toSchemaExportContextFromIdentifierNullSchemaContextNegativeTest() {
+        thrown.expect(NullPointerException.class);
+        ParserIdentifier.toSchemaExportContextFromIdentifier(null, TEST_MODULE_NAME + "/" + TEST_MODULE_REVISION, null);
+    }
+
+    /**
+     * Negative test of of getting <code>SchemaExportContext</code> when supplied <code>SchemaContext</code> is
+     * <code>null</code> and identifier specifies module behind mount point. Test is expected to fail with
+     * <code>NullPointerException</code>.
+     */
+    @Test
+    public void toSchemaExportContextFromIdentifierMountPointNullSchemaContextNegativeTest() {
+        thrown.expect(NullPointerException.class);
+        ParserIdentifier.toSchemaExportContextFromIdentifier(
+                null,
+                MOUNT_POINT_IDENT_WITHOUT_SLASH
+                + "/"
+                + TEST_MODULE_NAME
+                + "/"
+                + TEST_MODULE_REVISION,
+                mountPointService);
+    }
+
+    /**
+     * Negative test of of getting <code>SchemaExportContext</code> when supplied <code>DOMMountPointService</code>
+     * is <code>null</code> and identifier defines module behind mount point. Test is expected to fail with
+     * <code>NullPointerException</code>.
+     */
+    @Test
+    public void toSchemaExportContextFromIdentifierNullMountPointServiceNegativeTest() {
+        thrown.expect(NullPointerException.class);
+        ParserIdentifier.toSchemaExportContextFromIdentifier(
+                schemaContext,
+                MOUNT_POINT_IDENT_WITHOUT_SLASH
+                + "/"
+                + TEST_MODULE_NAME
+                + "/"
+                + TEST_MODULE_REVISION,
+                null);
+    }
+
+    /**
+     * Negative test of of getting <code>SchemaExportContext</code> when <code>SchemaContext</code> behind mount
+     * point is <code>null</code>. Test is expected to fail with <code>NullPointerException</code>.
+     */
+    @Test
+    public void toSchemaExportContextFromIdentifierNullSchemaContextBehindMountPointNegativeTest() {
+        thrown.expect(NullPointerException.class);
+        ParserIdentifier.toSchemaExportContextFromIdentifier(
+                schemaContext,
+                "/"
+                + RestconfConstants.MOUNT
+                + "/"
+                + TEST_MODULE_NAME
+                + "/"
+                + TEST_MODULE_REVISION,
+                mockMountPointService);
+    }
+}
diff --git a/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/utils/schema/context/RestconfSchemaUtilTest.java b/restconf/sal-rest-connector/src/test/java/org/opendaylight/restconf/utils/schema/context/RestconfSchemaUtilTest.java
new file mode 100644 (file)
index 0000000..7fd781a
--- /dev/null
@@ -0,0 +1,394 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. 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.restconf.utils.schema.context;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import static org.opendaylight.restconf.Draft11.MonitoringModule;
+import static org.opendaylight.restconf.Draft11.RestconfModule;
+
+import com.google.common.collect.Sets;
+import java.util.NoSuchElementException;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.opendaylight.controller.md.sal.rest.common.TestRestconfUtils;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException;
+import org.opendaylight.netconf.sal.restconf.impl.RestconfError;
+import org.opendaylight.restconf.Draft11;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+
+/**
+ * Unit tests for {@link RestconfSchemaUtil}
+ */
+public class RestconfSchemaUtilTest {
+    // schema with testing modules
+    private SchemaContext schemaContext;
+
+    @Rule
+    public ExpectedException thrown = ExpectedException.none();
+
+    @Before
+    public void setup() throws Exception {
+        schemaContext = TestRestconfUtils.loadSchemaContext("/modules/restconf-module-testing");
+    }
+
+    /**
+     * Positive test for getting <code>DataSchemaNode</code> from Restconf module for schema node name equals to
+     * {@link RestconfModule#MODULE_LIST_SCHEMA_NODE} when this node can be found.
+     */
+    @Test
+    public void getRestconfSchemaNodeListModuleTest() {
+        final DataSchemaNode dataSchemaNode = RestconfSchemaUtil.getRestconfSchemaNode(
+                getTestingRestconfModule("ietf-restconf"),
+                RestconfModule.MODULE_LIST_SCHEMA_NODE);
+
+        assertNotNull("Existing schema node "+ RestconfModule.MODULE_LIST_SCHEMA_NODE + " should be found",
+                dataSchemaNode);
+        assertEquals("Incorrect schema node was returned",
+                dataSchemaNode.getQName().getLocalName(), RestconfModule.MODULE_LIST_SCHEMA_NODE);
+        assertEquals("Incorrect schema node was returned",
+                dataSchemaNode.getQName().getNamespace().toString(), RestconfModule.NAMESPACE);
+        assertEquals("Incorrect schema node was returned",
+                dataSchemaNode.getQName().getFormattedRevision(), RestconfModule.REVISION);
+    }
+
+    /**
+     * Positive test for getting <code>DataSchemaNode</code> from Restconf module for schema node name equals to
+     * {@link MonitoringModule#STREAM_LIST_SCHEMA_NODE} when this node can be found.
+     */
+    @Test
+    public void getRestconfSchemaNodeListStreamTest() {
+        final DataSchemaNode dataSchemaNode = RestconfSchemaUtil.getRestconfSchemaNode(
+                getTestingRestconfModule("ietf-restconf"),
+                MonitoringModule.STREAM_LIST_SCHEMA_NODE);
+
+        assertNotNull("Existing schema node " + MonitoringModule.STREAM_LIST_SCHEMA_NODE + " should be found",
+                dataSchemaNode);
+        assertEquals("Incorrect schema node was returned",
+                dataSchemaNode.getQName().getLocalName(), MonitoringModule.STREAM_LIST_SCHEMA_NODE);
+        assertEquals("Incorrect schema node was returned",
+                dataSchemaNode.getQName().getNamespace().toString(), RestconfModule.NAMESPACE);
+        assertEquals("Incorrect schema node was returned",
+                dataSchemaNode.getQName().getFormattedRevision(), RestconfModule.REVISION);
+    }
+
+    /**
+     * Positive test for getting <code>DataSchemaNode</code> from Restconf module for schema node name equals to
+     * {@link RestconfModule#MODULES_CONTAINER_SCHEMA_NODE} when this node can be found.
+     */
+    @Test
+    public void getRestconfSchemaNodeContainerModulesTest() {
+        final DataSchemaNode dataSchemaNode = RestconfSchemaUtil.getRestconfSchemaNode(
+                getTestingRestconfModule("ietf-restconf"),
+                RestconfModule.MODULES_CONTAINER_SCHEMA_NODE);
+
+        assertNotNull("Existing schema node " + RestconfModule.MODULES_CONTAINER_SCHEMA_NODE + "should be found",
+                dataSchemaNode);
+        assertEquals("Incorrect schema node was returned",
+                dataSchemaNode.getQName().getLocalName(), RestconfModule.MODULES_CONTAINER_SCHEMA_NODE);
+        assertEquals("Incorrect schema node was returned",
+                dataSchemaNode.getQName().getNamespace().toString(), RestconfModule.NAMESPACE);
+        assertEquals("Incorrect schema node was returned",
+                dataSchemaNode.getQName().getFormattedRevision(), RestconfModule.REVISION);
+    }
+
+    /**
+     * Positive test for getting <code>DataSchemaNode</code> from Restconf module for schema node name equals to
+     * {@link MonitoringModule#STREAMS_CONTAINER_SCHEMA_NODE} when this node can be found.
+     */
+    @Test
+    public void getRestconfSchemaNodeContainerStreamsTest() {
+        final DataSchemaNode dataSchemaNode = RestconfSchemaUtil.getRestconfSchemaNode(
+                getTestingRestconfModule("ietf-restconf"),
+                MonitoringModule.STREAMS_CONTAINER_SCHEMA_NODE);
+
+        assertNotNull("Existing schema node " + MonitoringModule.STREAMS_CONTAINER_SCHEMA_NODE + " should be found",
+                dataSchemaNode);
+        assertEquals("Incorrect schema node was returned",
+                dataSchemaNode.getQName().getLocalName(), MonitoringModule.STREAMS_CONTAINER_SCHEMA_NODE);
+        assertEquals("Incorrect schema node was returned",
+                dataSchemaNode.getQName().getNamespace().toString(), RestconfModule.NAMESPACE);
+        assertEquals("Incorrect schema node was returned",
+                dataSchemaNode.getQName().getFormattedRevision(), RestconfModule.REVISION);
+    }
+
+    /**
+     * Negative test for getting <code>DataSchemaNode</code> from Restconf module when Restconf module is
+     * <code>null</code>. Test is expected to fail catching <code>NullPointerException</code>.
+     */
+    @Test
+    public void getRestconfSchemaNodeNullRestconfModuleNegativeTest() {
+        thrown.expect(NullPointerException.class);
+        RestconfSchemaUtil.getRestconfSchemaNode(null, RestconfModule.RESTCONF_CONTAINER_SCHEMA_NODE);
+    }
+
+    /**
+     * Negative test for getting <code>DataSchemaNode</code> from Restconf module when name of the schema node name is
+     * <code>null</code>. Test is expected to fail with <code>NullPointerException</code>.
+     */
+    @Test
+    public void getRestconfSchemaNodeNullSchemaNodeNameNegativeTest() {
+        thrown.expect(NullPointerException.class);
+        RestconfSchemaUtil.getRestconfSchemaNode(getTestingRestconfModule("ietf-restconf"), null);
+    }
+
+    /**
+     * Negative test for getting <code>DataSchemaNode</code> from Restconf module when name of the schema node name
+     * references to not existing node. Test is expected to fail catching code>RestconfDocumentedException</code> and
+     * checking expected error type, error tag and error status code.
+     */
+    @Test
+    public void getRestconfSchemaNodeNotExistingSchemaNodeNameNegativeTest() {
+        try {
+            RestconfSchemaUtil.getRestconfSchemaNode(getTestingRestconfModule("ietf-restconf"), "not-existing-node");
+            fail("Test should fail due to not-existing node name");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error type is not correct",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Error tag is not correct",
+                    RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+            assertEquals("Error status code is not correct",
+                    404, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test for getting <code>DataSchemaNode</code> from Restconf module for schema node name equals to
+     * {@link RestconfModule#MODULES_CONTAINER_SCHEMA_NODE} when this node cannot be found.
+     * <code>RestconfDocumentedException</code> is expected and error type, error tag and error status code are
+     * compared to expected values.
+     */
+    @Test
+    public void getRestconfSchemaNodeContainerModulesNegativeTest() {
+        try {
+            RestconfSchemaUtil.getRestconfSchemaNode(getTestingRestconfModule(
+                    "restconf-module-with-missing-container-modules"), RestconfModule.MODULES_CONTAINER_SCHEMA_NODE);
+            fail("Test should fail due to missing " + RestconfModule.MODULES_CONTAINER_SCHEMA_NODE + " node");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error type is not correct",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Error tag is not correct",
+                    RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+            assertEquals("Error status code is not correct",
+                    404, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test for getting <code>DataSchemaNode</code> from Restconf module for schema node name equals to
+     * {@link RestconfModule#MODULE_LIST_SCHEMA_NODE} when this node cannot be found.
+     * <code>RestconfDocumentedException</code> is expected and error type, error tag and error status code are
+     * compared to expected values.
+     */
+    @Test
+    public void getRestconfSchemaNodeListModuleNegativeTest() {
+        try {
+            RestconfSchemaUtil.getRestconfSchemaNode(
+                    getTestingRestconfModule("restconf-module-with-missing-list-module"),
+                    RestconfModule.MODULE_LIST_SCHEMA_NODE);
+            fail("Test should fail due to missing " + RestconfModule.MODULE_LIST_SCHEMA_NODE + " node");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error type is not correct",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Error tag is not correct",
+                    RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+            assertEquals("Error status code is not correct",
+                    404, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test for getting <code>DataSchemaNode</code> from Restconf module for schema node name equals to
+     * {@link MonitoringModule#STREAMS_CONTAINER_SCHEMA_NODE} when this node cannot
+     * be found. <code>RestconfDocumentedException</code> is expected and error type, error tag and error status code
+     * are compared to expected values.
+     */
+    @Test
+    public void getRestconfSchemaNodeContainerStreamsNegativeTest() {
+        try {
+            RestconfSchemaUtil.getRestconfSchemaNode(
+                    getTestingRestconfModule("restconf-module-with-missing-container-streams"),
+                    MonitoringModule.STREAMS_CONTAINER_SCHEMA_NODE);
+            fail("Test should fail due to missing " + MonitoringModule.STREAMS_CONTAINER_SCHEMA_NODE + " node");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error type is not correct",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Error tag is not correct",
+                    RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+            assertEquals("Error status code is not correct",
+                    404, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test for getting <code>DataSchemaNode</code> from Restconf module for schema node name equals to
+     * {@link MonitoringModule#STREAM_LIST_SCHEMA_NODE} when this node cannot be found.
+     * <code>RestconfDocumentedException</code> is expected and error type, error tag and error status code
+     * are compared to expected values.
+     */
+    @Test
+    public void getRestconfSchemaNodeListStreamNegativeTest() {
+        try {
+            RestconfSchemaUtil.getRestconfSchemaNode(
+                    getTestingRestconfModule("restconf-module-with-missing-list-stream"),
+                    MonitoringModule.STREAM_LIST_SCHEMA_NODE);
+            fail("Test should fail due to missing " + MonitoringModule.STREAM_LIST_SCHEMA_NODE + " node");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error type is not correct",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Error tag is not correct",
+                    RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+            assertEquals("Error status code is not correct",
+                    404, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test for getting <code>DataSchemaNode</code> from Restconf module when Restconf module does not
+     * contains restconf grouping. Test is expected to fail with <code>RestconfDocumentedException</code> and error
+     * type, error tag and error status code are compared to expected values.
+     */
+    @Test
+    public void getRestconfSchemaNodeMissingRestconfGroupingNegativeTest() {
+        try {
+            RestconfSchemaUtil.getRestconfSchemaNode(
+                    getTestingRestconfModule("restconf-module-with-missing-grouping-restconf"),
+                    RestconfModule.MODULES_CONTAINER_SCHEMA_NODE);
+            fail("Test should fail due to missing restconf grouping in Restconf module");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error type is not correct",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Error tag is not correct",
+                    RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+            assertEquals("Error status code is not correct",
+                    404, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test for getting <code>DataSchemaNode</code> from Restconf module when Restconf module contains
+     * restconf grouping which does not contain any child nodes. Test is expected to fail with
+     * <code>NoSuchElementException</code>.
+     */
+    @Test
+    public void getRestconfSchemaNodeEmptyRestconfGroupingNegativeTest() {
+        thrown.expect(NoSuchElementException.class);
+        RestconfSchemaUtil.getRestconfSchemaNode(
+                getTestingRestconfModule("restconf-module-with-empty-grouping-restconf"),
+                RestconfModule.MODULES_CONTAINER_SCHEMA_NODE);
+    }
+
+    /**
+     * Positive test trying to find <code>DataSchemaNode</code> of {@link RestconfModule#RESTCONF_GROUPING_SCHEMA_NODE}
+     * in Restconf module groupings collection.
+     */
+    @Test
+    public void findSchemaNodeInCollectionTest() {
+        final SchemaNode schemaNode = RestconfSchemaUtil.findSchemaNodeInCollection(
+                getTestingRestconfModule("ietf-restconf").getGroupings(),
+                RestconfModule.RESTCONF_GROUPING_SCHEMA_NODE);
+
+        assertNotNull("Restconf grouping schema node should be found", schemaNode);
+        assertEquals("Incorrect grouping was returned",
+                schemaNode.getQName().getLocalName(), RestconfModule.RESTCONF_GROUPING_SCHEMA_NODE);
+        assertEquals("Incorrect grouping was returned",
+                schemaNode.getQName().getNamespace().toString(), RestconfModule.NAMESPACE);
+        assertEquals("Incorrect grouping was returned",
+                schemaNode.getQName().getFormattedRevision(), RestconfModule.REVISION);
+    }
+
+    /**
+     * Negative test trying to find <code>DataSchemaNode</code> of not existing groupings schema node name in Restconf
+     * module grouping collection. Test is expected to fail catching <code>RestconfDocumentedException</code> and
+     * checking for correct error type, error tag and error status code.
+     */
+    @Test
+    public void findSchemaNodeInCollectionNegativeTest() {
+        try {
+            RestconfSchemaUtil.findSchemaNodeInCollection(
+                    getTestingRestconfModule("ietf-restconf").getGroupings(), "not-existing-grouping");
+            fail("Test should fail due to missing not-existing grouping in Restconf grouping collection");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error type is not correct",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Error tag is not correct",
+                    RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+            assertEquals("Error status code is not correct",
+                    404, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test trying to find <code>DataSchemaNode</code> of existing schema node name in <code>null</code>
+     * collection. Test is expected to fail with <code>NullPointerException</code>.
+     */
+    @Test
+    public void findSchemaNodeInCollectionNullCollectionNegativeTest() {
+        thrown.expect(NullPointerException.class);
+        RestconfSchemaUtil.findSchemaNodeInCollection(null, RestconfModule.MODULES_CONTAINER_SCHEMA_NODE);
+    }
+
+    /**
+     * Negative test trying to find <code>DataSchemaNode</code> for schema node name in empty collection. Test is
+     * expected to fail with <code>RestconfDocumentedException</code>. Error type, error tag and error status code
+     * are compared to expected values.
+     */
+    @Test
+    public void findSchemaNodeInCollectionEmptyCollectionNegativeTest() {
+        try {
+            RestconfSchemaUtil.findSchemaNodeInCollection(
+                    Sets.newHashSet(), RestconfModule.MODULES_CONTAINER_SCHEMA_NODE);
+            fail("Test should fail due to empty schema nodes collection");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error type is not correct",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Error tag is not correct",
+                    RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+            assertEquals("Error status code is not correct",
+                    404, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * Negative test trying to find <code>DataSchemaNode</code> of <code>null</code> schema node name in Restconf module
+     * groupings collection. Test is expected to fail with <code>RestconfDocumentedException</code>. Error type, error
+     * tag and error status code are compared to expected values.
+     */
+    @Test
+    public void findSchemaNodeInCollectionNullSchemaNodeName() {
+        try {
+            RestconfSchemaUtil.findSchemaNodeInCollection(
+                    getTestingRestconfModule("ietf-restconf").getGroupings(), null);
+            fail("Test should fail due to null schema node name");
+        } catch (final RestconfDocumentedException e) {
+            assertEquals("Error type is not correct",
+                    RestconfError.ErrorType.PROTOCOL, e.getErrors().get(0).getErrorType());
+            assertEquals("Error tag is not correct",
+                    RestconfError.ErrorTag.DATA_MISSING, e.getErrors().get(0).getErrorTag());
+            assertEquals("Error status code is not correct",
+                    404, e.getErrors().get(0).getErrorTag().getStatusCode());
+        }
+    }
+
+    /**
+     * There are multiple testing Restconf modules for different test cases. It is possible to distinguish them by
+     * name or by namespace. This method is looking for Restconf test module by its name.
+     * @param s Testing Restconf module name
+     * @return Restconf module
+     */
+    private Module getTestingRestconfModule(final String s) {
+        return schemaContext.findModuleByName(s, Draft11.RestconfModule.IETF_RESTCONF_QNAME.getRevision());
+    }
+}
diff --git a/restconf/sal-rest-connector/src/test/resources/modules/restconf-module-testing/restconf-module-with-empty-grouping-restconf.yang b/restconf/sal-rest-connector/src/test/resources/modules/restconf-module-testing/restconf-module-with-empty-grouping-restconf.yang
new file mode 100644 (file)
index 0000000..6e8377d
--- /dev/null
@@ -0,0 +1,519 @@
+module restconf-module-with-empty-grouping-restconf {
+     namespace "urn:ietf:params:xml:ns:yang:ietf-restconf-rmwegr";
+     prefix "restconf";
+
+     import ietf-yang-types { prefix yang; }
+     import ietf-inet-types { prefix inet; }
+
+     organization
+       "IETF NETCONF (Network Configuration) Working Group";
+
+     contact
+       "Editor:   Andy Bierman
+                  <mailto:andy@yumaworks.com>
+
+        Editor:   Martin Bjorklund
+                  <mailto:mbj@tail-f.com>
+
+        Editor:   Kent Watsen
+                  <mailto:kwatsen@juniper.net>
+
+        Editor:   Rex Fernando
+                  <mailto:rex@cisco.com>";
+
+     description
+       "This module contains conceptual YANG specifications
+        for the YANG Patch and error content that is used in
+        RESTCONF protocol messages. A conceptual container
+        representing the RESTCONF API nodes (media type
+        application/yang.api).
+
+        Note that the YANG definitions within this module do not
+        represent configuration data of any kind.
+        The YANG grouping statements provide a normative syntax
+        for XML and JSON message encoding purposes.
+        Copyright (c) 2013 IETF Trust and the persons identified as
+        authors of the code.  All rights reserved.
+
+        Redistribution and use in source and binary forms, with or
+        without modification, is permitted pursuant to, and subject
+        to the license terms contained in, the Simplified BSD License
+        set forth in Section 4.c of the IETF Trust's Legal Provisions
+        Relating to IETF Documents
+        (http://trustee.ietf.org/license-info).
+
+        This version of this YANG module is part of RFC XXXX; see
+        the RFC itself for full legal notices.";
+
+     // RFC Ed.: replace XXXX with actual RFC number and remove this
+     // note.
+
+     // RFC Ed.: remove this note
+     // Note: extracted from draft-bierman-netconf-restconf-02.txt
+
+     // RFC Ed.: update the date below with the date of RFC publication
+     // and remove this note.
+     revision 2013-10-19 {
+       description
+         "Initial revision.";
+       reference
+         "RFC XXXX: RESTCONF Protocol.";
+     }
+
+     typedef data-resource-identifier {
+       type string {
+         length "1 .. max";
+       }
+       description
+         "Contains a Data Resource Identifier formatted string
+          to identify a specific data node. The data node that
+          uses this data type SHOULD define the document root
+          for data resource identifiers.  The default document
+          root is the target datastore conceptual root node.
+          Data resource identifiers are defined relative to
+          this document root.";
+       reference
+         "RFC XXXX: [sec. 5.3.1.1 ABNF For Data Resource Identifiers]";
+     }
+
+     // this typedef is TBD; not currently used
+     typedef datastore-identifier {
+       type union {
+         type enumeration {
+           enum candidate {
+             description
+               "Identifies the NETCONF shared candidate datastore.";
+             reference
+               "RFC 6241, section 8.3";
+           }
+           enum running {
+             description
+               "Identifies the NETCONF running datastore.";
+             reference
+               "RFC 6241, section 5.1";
+           }
+           enum startup {
+             description
+               "Identifies the NETCONF startup datastore.";
+             reference
+               "RFC 6241, section 8.7";
+           }
+         }
+         type string;
+       }
+       description
+         "Contains a string to identify a specific datastore.
+          The enumerated datastore identifier values are
+          reserved for standard datastore names.";
+     }
+
+     typedef revision-identifier {
+       type string {
+         pattern '\d{4}-\d{2}-\d{2}';
+       }
+       description
+         "Represents a specific date in YYYY-MM-DD format.
+          TBD: make pattern more precise to exclude leading zeros.";
+     }
+
+     grouping yang-patch {
+       description
+         "A grouping that contains a YANG container
+          representing the syntax and semantics of a
+          YANG Patch edit request message.";
+
+       container yang-patch {
+         description
+           "Represents a conceptual sequence of datastore edits,
+            called a patch. Each patch is given a client-assigned
+            patch identifier. Each edit MUST be applied
+            in ascending order, and all edits MUST be applied.
+            If any errors occur, then the target datastore MUST NOT
+            be changed by the patch operation.
+
+            A patch MUST be validated by the server to be a
+            well-formed message before any of the patch edits
+            are validated or attempted.
+
+            YANG datastore validation (defined in RFC 6020, section
+            8.3.3) is performed after all edits have been
+            individually validated.
+
+            It is possible for a datastore constraint violation to occur
+            due to any node in the datastore, including nodes not
+            included in the edit list. Any validation errors MUST
+            be reported in the reply message.";
+
+         reference
+           "RFC 6020, section 8.3.";
+
+         leaf patch-id {
+           type string;
+           description
+             "An arbitrary string provided by the client to identify
+              the entire patch.  This value SHOULD be present in any
+              audit logging records generated by the server for the
+              patch. Error messages returned by the server pertaining
+              to this patch will be identified by this patch-id value.";
+         }
+
+         leaf comment {
+           type string {
+             length "0 .. 1024";
+           }
+           description
+             "An arbitrary string provided by the client to describe
+              the entire patch.  This value SHOULD be present in any
+              audit logging records generated by the server for the
+              patch.";
+         }
+
+         list edit {
+           key edit-id;
+           ordered-by user;
+
+           description
+             "Represents one edit within the YANG Patch
+              request message.";
+           leaf edit-id {
+             type string;
+             description
+               "Arbitrary string index for the edit.
+                Error messages returned by the server pertaining
+                to a specific edit will be identified by this
+                value.";
+           }
+
+           leaf operation {
+             type enumeration {
+               enum create {
+                 description
+                   "The target data node is created using the
+                    supplied value, only if it does not already
+                    exist.";
+               }
+               enum delete {
+                 description
+                   "Delete the target node, only if the data resource
+                    currently exists, otherwise return an error.";
+               }
+               enum insert {
+                 description
+                   "Insert the supplied value into a user-ordered
+                    list or leaf-list entry. The target node must
+                    represent a new data resource.";
+               }
+               enum merge {
+                 description
+                   "The supplied value is merged with the target data
+                    node.";
+               }
+               enum move {
+                 description
+                   "Move the target node. Reorder a user-ordered
+                    list or leaf-list. The target node must represent
+                    an existing data resource.";
+               }
+               enum replace {
+                 description
+                   "The supplied value is used to replace the target
+                    data node.";
+               }
+               enum remove {
+                 description
+                   "Delete the target node if it currently exists.";
+               }
+             }
+             mandatory true;
+             description
+               "The datastore operation requested for the associated
+                edit entry";
+           }
+
+           leaf target {
+             type data-resource-identifier;
+             mandatory true;
+             description
+               "Identifies the target data resource for the edit
+                operation.";
+           }
+
+           leaf point {
+             when "(../operation = 'insert' or " +
+               "../operation = 'move') and " +
+               "(../where = 'before' or ../where = 'after')" {
+               description
+                 "Point leaf only applies for insert or move
+                  operations, before or after an existing entry.";
+             }
+             type data-resource-identifier;
+             description
+               "The absolute URL path for the data node that is being
+                used as the insertion point or move point for the
+                target of this edit entry.";
+           }
+
+           leaf where {
+             when "../operation = 'insert' or ../operation = 'move'" {
+               description
+                 "Where leaf only applies for insert or move
+                  operations.";
+             }
+             type enumeration {
+               enum before {
+                 description
+                   "Insert or move a data node before the data resource
+                    identified by the 'point' parameter.";
+               }
+               enum after {
+                 description
+                   "Insert or move a data node after the data resource
+                    identified by the 'point' parameter.";
+               }
+               enum first {
+                 description
+                   "Insert or move a data node so it becomes ordered
+                    as the first entry.";
+               }
+               enum last {
+                 description
+                   "Insert or move a data node so it becomes ordered
+                    as the last entry.";
+               }
+
+             }
+             default last;
+             description
+               "Identifies where a data resource will be inserted or
+                moved. YANG only allows these operations for
+                list and leaf-list data nodes that are ordered-by
+                user.";
+           }
+
+           anyxml value {
+             when "(../operation = 'create' or " +
+               "../operation = 'merge' " +
+               "or ../operation = 'replace' or " +
+               "../operation = 'insert')" {
+               description
+                 "Value node only used for create, merge,
+                  replace, and insert operations";
+             }
+             description
+               "Value used for this edit operation.";
+           }
+         }
+       }
+
+     } // grouping yang-patch
+
+
+     grouping yang-patch-status {
+
+       description
+         "A grouping that contains a YANG container
+          representing the syntax and semantics of
+          YANG Patch status response message.";
+
+       container yang-patch-status {
+         description
+           "A container representing the response message
+            sent by the server after a YANG Patch edit
+            request message has been processed.";
+
+         leaf patch-id {
+           type string;
+           description
+             "The patch-id value used in the request";
+         }
+
+         choice global-status {
+           description
+             "Report global errors or complete success.
+              If there is no case selected then errors
+              are reported in the edit-status container.";
+
+           case global-errors {
+             uses errors;
+             description
+               "This container will be present if global
+                errors unrelated to a specific edit occurred.";
+           }
+           leaf ok {
+             type empty;
+             description
+               "This leaf will be present if the request succeeded
+                and there are no errors reported in the edit-status
+                container.";
+           }
+         }
+
+         container edit-status {
+           description
+             "This container will be present if there are
+              edit-specific status responses to report.";
+
+           list edit {
+             key edit-id;
+
+             description
+               "Represents a list of status responses,
+                corresponding to edits in the YANG Patch
+                request message.  If an edit entry was
+                skipped or not reached by the server,
+                then this list will not contain a corresponding
+                entry for that edit.";
+
+             leaf edit-id {
+               type string;
+                description
+                  "Response status is for the edit list entry
+                   with this edit-id value.";
+             }
+             choice edit-status-choice {
+               description
+                 "A choice between different types of status
+                  responses for each edit entry.";
+               leaf ok {
+                 type empty;
+                 description
+                   "This edit entry was invoked without any
+                    errors detected by the server associated
+                    with this edit.";
+               }
+               leaf location {
+                 type inet:uri;
+                 description
+                   "Contains the Location header value that would be
+                    returned if this edit causes a new resource to be
+                    created. If the edit identified by the same edit-id
+                    value was successfully invoked and a new resource
+                    was created, then this field will be returned
+                    instead of 'ok'.";
+               }
+               case errors {
+                 uses errors;
+                 description
+                   "The server detected errors associated with the
+                     edit identified by the same edit-id value.";
+               }
+             }
+           }
+         }
+       }
+     }  // grouping yang-patch-status
+
+
+     grouping errors {
+
+       description
+         "A grouping that contains a YANG container
+          representing the syntax and semantics of a
+          YANG Patch errors report within a response message.";
+
+       container errors {
+         config false;  // needed so list error does not need a key
+         description
+           "Represents an error report returned by the server if
+            a request results in an error.";
+
+         list error {
+           description
+             "An entry containing information about one
+              specific error that occurred while processing
+              a RESTCONF request.";
+           reference "RFC 6241, Section 4.3";
+
+           leaf error-type {
+             type enumeration {
+               enum transport {
+                 description "The transport layer";
+               }
+               enum rpc {
+                 description "The rpc or notification layer";
+               }
+               enum protocol {
+                 description "The protocol operation layer";
+               }
+               enum application {
+                 description "The server application layer";
+               }
+             }
+             mandatory true;
+             description
+               "The protocol layer where the error occurred.";
+           }
+
+           leaf error-tag {
+             type string;
+             mandatory true;
+             description
+               "The enumerated error tag.";
+           }
+
+           leaf error-app-tag {
+             type string;
+             description
+               "The application-specific error tag.";
+           }
+
+           leaf error-path {
+             type data-resource-identifier;
+             description
+               "The target data resource identifier associated
+                with the error, if any.";
+           }
+           leaf error-message {
+             type string;
+             description
+               "A message describing the error.";
+           }
+
+           container error-info {
+              description
+                "A container allowing additional information
+                 to be included in the error report.";
+              // arbitrary anyxml content here
+           }
+         }
+       }
+     } // grouping errors
+
+     /** This grouping is empty for testing purposes  **/
+     grouping restconf {}
+
+     grouping notification {
+       description
+         "Contains the notification message wrapper definition.";
+
+       container notification {
+         description
+           "RESTCONF notification message wrapper.";
+         leaf event-time {
+           type yang:date-and-time;
+           mandatory true;
+           description
+             "The time the event was generated by the
+              event source.";
+           reference
+             "RFC 5277, section 4, <eventTime> element.";
+         }
+
+         /* The YANG-specific notification container is encoded
+          * after the 'event-time' element.  The format
+          * corresponds to the notificationContent element
+          * in RFC 5277, section 4. For example:
+          *
+          *  module example-one {
+          *     ...
+          *     notification event1 { ... }
+          *
+          *  }
+          *
+          *  Encoded as element 'event1' in the namespace
+          *  for module 'example-one'.
+          */
+       }
+     }  // grouping notification
+
+   }
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/modules/restconf-module-testing/restconf-module-with-missing-container-modules.yang b/restconf/sal-rest-connector/src/test/resources/modules/restconf-module-testing/restconf-module-with-missing-container-modules.yang
new file mode 100644 (file)
index 0000000..c117ffb
--- /dev/null
@@ -0,0 +1,639 @@
+module restconf-module-with-missing-container-modules {
+     namespace "urn:ietf:params:xml:ns:yang:ietf-restcon-rmwmcmf";
+     prefix "restconf";
+
+     import ietf-yang-types { prefix yang; }
+     import ietf-inet-types { prefix inet; }
+
+     organization
+       "IETF NETCONF (Network Configuration) Working Group";
+
+     contact
+       "Editor:   Andy Bierman
+                  <mailto:andy@yumaworks.com>
+
+        Editor:   Martin Bjorklund
+                  <mailto:mbj@tail-f.com>
+
+        Editor:   Kent Watsen
+                  <mailto:kwatsen@juniper.net>
+
+        Editor:   Rex Fernando
+                  <mailto:rex@cisco.com>";
+
+     description
+       "This module contains conceptual YANG specifications
+        for the YANG Patch and error content that is used in
+        RESTCONF protocol messages. A conceptual container
+        representing the RESTCONF API nodes (media type
+        application/yang.api).
+
+        Note that the YANG definitions within this module do not
+        represent configuration data of any kind.
+        The YANG grouping statements provide a normative syntax
+        for XML and JSON message encoding purposes.
+        Copyright (c) 2013 IETF Trust and the persons identified as
+        authors of the code.  All rights reserved.
+
+        Redistribution and use in source and binary forms, with or
+        without modification, is permitted pursuant to, and subject
+        to the license terms contained in, the Simplified BSD License
+        set forth in Section 4.c of the IETF Trust's Legal Provisions
+        Relating to IETF Documents
+        (http://trustee.ietf.org/license-info).
+
+        This version of this YANG module is part of RFC XXXX; see
+        the RFC itself for full legal notices.";
+
+     // RFC Ed.: replace XXXX with actual RFC number and remove this
+     // note.
+
+     // RFC Ed.: remove this note
+     // Note: extracted from draft-bierman-netconf-restconf-02.txt
+
+     // RFC Ed.: update the date below with the date of RFC publication
+     // and remove this note.
+     revision 2013-10-19 {
+       description
+         "Initial revision.";
+       reference
+         "RFC XXXX: RESTCONF Protocol.";
+     }
+
+     typedef data-resource-identifier {
+       type string {
+         length "1 .. max";
+       }
+       description
+         "Contains a Data Resource Identifier formatted string
+          to identify a specific data node. The data node that
+          uses this data type SHOULD define the document root
+          for data resource identifiers.  The default document
+          root is the target datastore conceptual root node.
+          Data resource identifiers are defined relative to
+          this document root.";
+       reference
+         "RFC XXXX: [sec. 5.3.1.1 ABNF For Data Resource Identifiers]";
+     }
+
+     // this typedef is TBD; not currently used
+     typedef datastore-identifier {
+       type union {
+         type enumeration {
+           enum candidate {
+             description
+               "Identifies the NETCONF shared candidate datastore.";
+             reference
+               "RFC 6241, section 8.3";
+           }
+           enum running {
+             description
+               "Identifies the NETCONF running datastore.";
+             reference
+               "RFC 6241, section 5.1";
+           }
+           enum startup {
+             description
+               "Identifies the NETCONF startup datastore.";
+             reference
+               "RFC 6241, section 8.7";
+           }
+         }
+         type string;
+       }
+       description
+         "Contains a string to identify a specific datastore.
+          The enumerated datastore identifier values are
+          reserved for standard datastore names.";
+     }
+
+     typedef revision-identifier {
+       type string {
+         pattern '\d{4}-\d{2}-\d{2}';
+       }
+       description
+         "Represents a specific date in YYYY-MM-DD format.
+          TBD: make pattern more precise to exclude leading zeros.";
+     }
+
+     grouping yang-patch {
+       description
+         "A grouping that contains a YANG container
+          representing the syntax and semantics of a
+          YANG Patch edit request message.";
+
+       container yang-patch {
+         description
+           "Represents a conceptual sequence of datastore edits,
+            called a patch. Each patch is given a client-assigned
+            patch identifier. Each edit MUST be applied
+            in ascending order, and all edits MUST be applied.
+            If any errors occur, then the target datastore MUST NOT
+            be changed by the patch operation.
+
+            A patch MUST be validated by the server to be a
+            well-formed message before any of the patch edits
+            are validated or attempted.
+
+            YANG datastore validation (defined in RFC 6020, section
+            8.3.3) is performed after all edits have been
+            individually validated.
+
+            It is possible for a datastore constraint violation to occur
+            due to any node in the datastore, including nodes not
+            included in the edit list. Any validation errors MUST
+            be reported in the reply message.";
+
+         reference
+           "RFC 6020, section 8.3.";
+
+         leaf patch-id {
+           type string;
+           description
+             "An arbitrary string provided by the client to identify
+              the entire patch.  This value SHOULD be present in any
+              audit logging records generated by the server for the
+              patch. Error messages returned by the server pertaining
+              to this patch will be identified by this patch-id value.";
+         }
+
+         leaf comment {
+           type string {
+             length "0 .. 1024";
+           }
+           description
+             "An arbitrary string provided by the client to describe
+              the entire patch.  This value SHOULD be present in any
+              audit logging records generated by the server for the
+              patch.";
+         }
+
+         list edit {
+           key edit-id;
+           ordered-by user;
+
+           description
+             "Represents one edit within the YANG Patch
+              request message.";
+           leaf edit-id {
+             type string;
+             description
+               "Arbitrary string index for the edit.
+                Error messages returned by the server pertaining
+                to a specific edit will be identified by this
+                value.";
+           }
+
+           leaf operation {
+             type enumeration {
+               enum create {
+                 description
+                   "The target data node is created using the
+                    supplied value, only if it does not already
+                    exist.";
+               }
+               enum delete {
+                 description
+                   "Delete the target node, only if the data resource
+                    currently exists, otherwise return an error.";
+               }
+               enum insert {
+                 description
+                   "Insert the supplied value into a user-ordered
+                    list or leaf-list entry. The target node must
+                    represent a new data resource.";
+               }
+               enum merge {
+                 description
+                   "The supplied value is merged with the target data
+                    node.";
+               }
+               enum move {
+                 description
+                   "Move the target node. Reorder a user-ordered
+                    list or leaf-list. The target node must represent
+                    an existing data resource.";
+               }
+               enum replace {
+                 description
+                   "The supplied value is used to replace the target
+                    data node.";
+               }
+               enum remove {
+                 description
+                   "Delete the target node if it currently exists.";
+               }
+             }
+             mandatory true;
+             description
+               "The datastore operation requested for the associated
+                edit entry";
+           }
+
+           leaf target {
+             type data-resource-identifier;
+             mandatory true;
+             description
+               "Identifies the target data resource for the edit
+                operation.";
+           }
+
+           leaf point {
+             when "(../operation = 'insert' or " +
+               "../operation = 'move') and " +
+               "(../where = 'before' or ../where = 'after')" {
+               description
+                 "Point leaf only applies for insert or move
+                  operations, before or after an existing entry.";
+             }
+             type data-resource-identifier;
+             description
+               "The absolute URL path for the data node that is being
+                used as the insertion point or move point for the
+                target of this edit entry.";
+           }
+
+           leaf where {
+             when "../operation = 'insert' or ../operation = 'move'" {
+               description
+                 "Where leaf only applies for insert or move
+                  operations.";
+             }
+             type enumeration {
+               enum before {
+                 description
+                   "Insert or move a data node before the data resource
+                    identified by the 'point' parameter.";
+               }
+               enum after {
+                 description
+                   "Insert or move a data node after the data resource
+                    identified by the 'point' parameter.";
+               }
+               enum first {
+                 description
+                   "Insert or move a data node so it becomes ordered
+                    as the first entry.";
+               }
+               enum last {
+                 description
+                   "Insert or move a data node so it becomes ordered
+                    as the last entry.";
+               }
+
+             }
+             default last;
+             description
+               "Identifies where a data resource will be inserted or
+                moved. YANG only allows these operations for
+                list and leaf-list data nodes that are ordered-by
+                user.";
+           }
+
+           anyxml value {
+             when "(../operation = 'create' or " +
+               "../operation = 'merge' " +
+               "or ../operation = 'replace' or " +
+               "../operation = 'insert')" {
+               description
+                 "Value node only used for create, merge,
+                  replace, and insert operations";
+             }
+             description
+               "Value used for this edit operation.";
+           }
+         }
+       }
+
+     } // grouping yang-patch
+
+
+     grouping yang-patch-status {
+
+       description
+         "A grouping that contains a YANG container
+          representing the syntax and semantics of
+          YANG Patch status response message.";
+
+       container yang-patch-status {
+         description
+           "A container representing the response message
+            sent by the server after a YANG Patch edit
+            request message has been processed.";
+
+         leaf patch-id {
+           type string;
+           description
+             "The patch-id value used in the request";
+         }
+
+         choice global-status {
+           description
+             "Report global errors or complete success.
+              If there is no case selected then errors
+              are reported in the edit-status container.";
+
+           case global-errors {
+             uses errors;
+             description
+               "This container will be present if global
+                errors unrelated to a specific edit occurred.";
+           }
+           leaf ok {
+             type empty;
+             description
+               "This leaf will be present if the request succeeded
+                and there are no errors reported in the edit-status
+                container.";
+           }
+         }
+
+         container edit-status {
+           description
+             "This container will be present if there are
+              edit-specific status responses to report.";
+
+           list edit {
+             key edit-id;
+
+             description
+               "Represents a list of status responses,
+                corresponding to edits in the YANG Patch
+                request message.  If an edit entry was
+                skipped or not reached by the server,
+                then this list will not contain a corresponding
+                entry for that edit.";
+
+             leaf edit-id {
+               type string;
+                description
+                  "Response status is for the edit list entry
+                   with this edit-id value.";
+             }
+             choice edit-status-choice {
+               description
+                 "A choice between different types of status
+                  responses for each edit entry.";
+               leaf ok {
+                 type empty;
+                 description
+                   "This edit entry was invoked without any
+                    errors detected by the server associated
+                    with this edit.";
+               }
+               leaf location {
+                 type inet:uri;
+                 description
+                   "Contains the Location header value that would be
+                    returned if this edit causes a new resource to be
+                    created. If the edit identified by the same edit-id
+                    value was successfully invoked and a new resource
+                    was created, then this field will be returned
+                    instead of 'ok'.";
+               }
+               case errors {
+                 uses errors;
+                 description
+                   "The server detected errors associated with the
+                     edit identified by the same edit-id value.";
+               }
+             }
+           }
+         }
+       }
+     }  // grouping yang-patch-status
+
+
+     grouping errors {
+
+       description
+         "A grouping that contains a YANG container
+          representing the syntax and semantics of a
+          YANG Patch errors report within a response message.";
+
+       container errors {
+         config false;  // needed so list error does not need a key
+         description
+           "Represents an error report returned by the server if
+            a request results in an error.";
+
+         list error {
+           description
+             "An entry containing information about one
+              specific error that occurred while processing
+              a RESTCONF request.";
+           reference "RFC 6241, Section 4.3";
+
+           leaf error-type {
+             type enumeration {
+               enum transport {
+                 description "The transport layer";
+               }
+               enum rpc {
+                 description "The rpc or notification layer";
+               }
+               enum protocol {
+                 description "The protocol operation layer";
+               }
+               enum application {
+                 description "The server application layer";
+               }
+             }
+             mandatory true;
+             description
+               "The protocol layer where the error occurred.";
+           }
+
+           leaf error-tag {
+             type string;
+             mandatory true;
+             description
+               "The enumerated error tag.";
+           }
+
+           leaf error-app-tag {
+             type string;
+             description
+               "The application-specific error tag.";
+           }
+
+           leaf error-path {
+             type data-resource-identifier;
+             description
+               "The target data resource identifier associated
+                with the error, if any.";
+           }
+           leaf error-message {
+             type string;
+             description
+               "A message describing the error.";
+           }
+
+           container error-info {
+              description
+                "A container allowing additional information
+                 to be included in the error report.";
+              // arbitrary anyxml content here
+           }
+         }
+       }
+     } // grouping errors
+
+
+     grouping restconf {
+
+       description
+         "A grouping that contains a YANG container
+          representing the syntax and semantics of
+          the RESTCONF API resource.";
+
+       container restconf {
+         description
+           "Conceptual container representing the
+            application/yang.api resource type.";
+
+         container config {
+           description
+             "Container representing the application/yang.datastore
+              resource type. Represents the conceptual root of the
+              unified configuration datastore containing YANG data
+              nodes. The child nodes of this container are
+              configuration data resources (application/yang.data)
+              defined as top-level YANG data nodes from the modules
+              advertised by the server in /restconf/modules.";
+         }
+
+         container operational {
+           description
+             "Container representing the application/yang.datastore
+              resource type. Represents the conceptual root of the
+              operational data supported by the server.  The child
+              nodes of this container are operational data resources
+              (application/yang.data) defined as top-level
+              YANG data nodes from the modules advertised by
+              the server in /restconf/modules.";
+         }
+
+         // removed container modules for testing purposes
+
+         container operations {
+           description
+             "Container for all operation resources
+              (application/yang.operation),
+
+              Each resource is represented as an empty leaf with the
+              name of the RPC operation from the YANG rpc statement.
+
+              E.g.;
+
+                 POST /restconf/operations/show-log-errors
+
+                 leaf show-log-errors {
+                   type empty;
+                 }
+             ";
+         }
+
+         container streams {
+           description
+             "Container representing the notification event streams
+              supported by the server.";
+            reference
+              "RFC 5277, Section 3.4, <streams> element.";
+
+           list stream {
+             key name;
+             description
+               "Each entry describes an event stream supported by
+                the server.";
+
+             leaf name {
+               type string;
+               description "The stream name";
+               reference "RFC 5277, Section 3.4, <name> element.";
+             }
+
+             leaf description {
+               type string;
+               description "Description of stream content";
+               reference
+                 "RFC 5277, Section 3.4, <description> element.";
+             }
+
+             leaf replay-support {
+               type boolean;
+               description
+                 "Indicates if replay buffer supported for this stream";
+               reference
+                 "RFC 5277, Section 3.4, <replaySupport> element.";
+             }
+
+             leaf replay-log-creation-time {
+               type yang:date-and-time;
+               description
+                 "Indicates the time the replay log for this stream
+                  was created.";
+               reference
+                 "RFC 5277, Section 3.4, <replayLogCreationTime>
+                  element.";
+             }
+
+             leaf events {
+               type empty;
+               description
+                 "Represents the entry point for establishing
+                  notification delivery via server sent events.";
+             }
+           }
+         }
+
+         leaf version {
+           type enumeration {
+             enum "1.0" {
+               description
+                 "Version 1.0 of the RESTCONF protocol.";
+             }
+           }
+           config false;
+           description
+             "Contains the RESTCONF protocol version.";
+         }
+       }
+     }  // grouping restconf
+
+
+     grouping notification {
+       description
+         "Contains the notification message wrapper definition.";
+
+       container notification {
+         description
+           "RESTCONF notification message wrapper.";
+         leaf event-time {
+           type yang:date-and-time;
+           mandatory true;
+           description
+             "The time the event was generated by the
+              event source.";
+           reference
+             "RFC 5277, section 4, <eventTime> element.";
+         }
+
+         /* The YANG-specific notification container is encoded
+          * after the 'event-time' element.  The format
+          * corresponds to the notificationContent element
+          * in RFC 5277, section 4. For example:
+          *
+          *  module example-one {
+          *     ...
+          *     notification event1 { ... }
+          *
+          *  }
+          *
+          *  Encoded as element 'event1' in the namespace
+          *  for module 'example-one'.
+          */
+       }
+     }  // grouping notification
+
+   }
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/modules/restconf-module-testing/restconf-module-with-missing-grouping-restconf.yang b/restconf/sal-rest-connector/src/test/resources/modules/restconf-module-testing/restconf-module-with-missing-grouping-restconf.yang
new file mode 100644 (file)
index 0000000..0e8009a
--- /dev/null
@@ -0,0 +1,518 @@
+module restconf-module-with-missing-grouping-restconf {
+     namespace "urn:ietf:params:xml:ns:yang:ietf-restconf-rmwmgr";
+     prefix "restconf";
+
+     import ietf-yang-types { prefix yang; }
+     import ietf-inet-types { prefix inet; }
+
+     organization
+       "IETF NETCONF (Network Configuration) Working Group";
+
+     contact
+       "Editor:   Andy Bierman
+                  <mailto:andy@yumaworks.com>
+
+        Editor:   Martin Bjorklund
+                  <mailto:mbj@tail-f.com>
+
+        Editor:   Kent Watsen
+                  <mailto:kwatsen@juniper.net>
+
+        Editor:   Rex Fernando
+                  <mailto:rex@cisco.com>";
+
+     description
+       "This module contains conceptual YANG specifications
+        for the YANG Patch and error content that is used in
+        RESTCONF protocol messages. A conceptual container
+        representing the RESTCONF API nodes (media type
+        application/yang.api).
+
+        Note that the YANG definitions within this module do not
+        represent configuration data of any kind.
+        The YANG grouping statements provide a normative syntax
+        for XML and JSON message encoding purposes.
+        Copyright (c) 2013 IETF Trust and the persons identified as
+        authors of the code.  All rights reserved.
+
+        Redistribution and use in source and binary forms, with or
+        without modification, is permitted pursuant to, and subject
+        to the license terms contained in, the Simplified BSD License
+        set forth in Section 4.c of the IETF Trust's Legal Provisions
+        Relating to IETF Documents
+        (http://trustee.ietf.org/license-info).
+
+        This version of this YANG module is part of RFC XXXX; see
+        the RFC itself for full legal notices.";
+
+     // RFC Ed.: replace XXXX with actual RFC number and remove this
+     // note.
+
+     // RFC Ed.: remove this note
+     // Note: extracted from draft-bierman-netconf-restconf-02.txt
+
+     // RFC Ed.: update the date below with the date of RFC publication
+     // and remove this note.
+     revision 2013-10-19 {
+       description
+         "Initial revision.";
+       reference
+         "RFC XXXX: RESTCONF Protocol.";
+     }
+
+     typedef data-resource-identifier {
+       type string {
+         length "1 .. max";
+       }
+       description
+         "Contains a Data Resource Identifier formatted string
+          to identify a specific data node. The data node that
+          uses this data type SHOULD define the document root
+          for data resource identifiers.  The default document
+          root is the target datastore conceptual root node.
+          Data resource identifiers are defined relative to
+          this document root.";
+       reference
+         "RFC XXXX: [sec. 5.3.1.1 ABNF For Data Resource Identifiers]";
+     }
+
+     // this typedef is TBD; not currently used
+     typedef datastore-identifier {
+       type union {
+         type enumeration {
+           enum candidate {
+             description
+               "Identifies the NETCONF shared candidate datastore.";
+             reference
+               "RFC 6241, section 8.3";
+           }
+           enum running {
+             description
+               "Identifies the NETCONF running datastore.";
+             reference
+               "RFC 6241, section 5.1";
+           }
+           enum startup {
+             description
+               "Identifies the NETCONF startup datastore.";
+             reference
+               "RFC 6241, section 8.7";
+           }
+         }
+         type string;
+       }
+       description
+         "Contains a string to identify a specific datastore.
+          The enumerated datastore identifier values are
+          reserved for standard datastore names.";
+     }
+
+     typedef revision-identifier {
+       type string {
+         pattern '\d{4}-\d{2}-\d{2}';
+       }
+       description
+         "Represents a specific date in YYYY-MM-DD format.
+          TBD: make pattern more precise to exclude leading zeros.";
+     }
+
+     grouping yang-patch {
+       description
+         "A grouping that contains a YANG container
+          representing the syntax and semantics of a
+          YANG Patch edit request message.";
+
+       container yang-patch {
+         description
+           "Represents a conceptual sequence of datastore edits,
+            called a patch. Each patch is given a client-assigned
+            patch identifier. Each edit MUST be applied
+            in ascending order, and all edits MUST be applied.
+            If any errors occur, then the target datastore MUST NOT
+            be changed by the patch operation.
+
+            A patch MUST be validated by the server to be a
+            well-formed message before any of the patch edits
+            are validated or attempted.
+
+            YANG datastore validation (defined in RFC 6020, section
+            8.3.3) is performed after all edits have been
+            individually validated.
+
+            It is possible for a datastore constraint violation to occur
+            due to any node in the datastore, including nodes not
+            included in the edit list. Any validation errors MUST
+            be reported in the reply message.";
+
+         reference
+           "RFC 6020, section 8.3.";
+
+         leaf patch-id {
+           type string;
+           description
+             "An arbitrary string provided by the client to identify
+              the entire patch.  This value SHOULD be present in any
+              audit logging records generated by the server for the
+              patch. Error messages returned by the server pertaining
+              to this patch will be identified by this patch-id value.";
+         }
+
+         leaf comment {
+           type string {
+             length "0 .. 1024";
+           }
+           description
+             "An arbitrary string provided by the client to describe
+              the entire patch.  This value SHOULD be present in any
+              audit logging records generated by the server for the
+              patch.";
+         }
+
+         list edit {
+           key edit-id;
+           ordered-by user;
+
+           description
+             "Represents one edit within the YANG Patch
+              request message.";
+           leaf edit-id {
+             type string;
+             description
+               "Arbitrary string index for the edit.
+                Error messages returned by the server pertaining
+                to a specific edit will be identified by this
+                value.";
+           }
+
+           leaf operation {
+             type enumeration {
+               enum create {
+                 description
+                   "The target data node is created using the
+                    supplied value, only if it does not already
+                    exist.";
+               }
+               enum delete {
+                 description
+                   "Delete the target node, only if the data resource
+                    currently exists, otherwise return an error.";
+               }
+               enum insert {
+                 description
+                   "Insert the supplied value into a user-ordered
+                    list or leaf-list entry. The target node must
+                    represent a new data resource.";
+               }
+               enum merge {
+                 description
+                   "The supplied value is merged with the target data
+                    node.";
+               }
+               enum move {
+                 description
+                   "Move the target node. Reorder a user-ordered
+                    list or leaf-list. The target node must represent
+                    an existing data resource.";
+               }
+               enum replace {
+                 description
+                   "The supplied value is used to replace the target
+                    data node.";
+               }
+               enum remove {
+                 description
+                   "Delete the target node if it currently exists.";
+               }
+             }
+             mandatory true;
+             description
+               "The datastore operation requested for the associated
+                edit entry";
+           }
+
+           leaf target {
+             type data-resource-identifier;
+             mandatory true;
+             description
+               "Identifies the target data resource for the edit
+                operation.";
+           }
+
+           leaf point {
+             when "(../operation = 'insert' or " +
+               "../operation = 'move') and " +
+               "(../where = 'before' or ../where = 'after')" {
+               description
+                 "Point leaf only applies for insert or move
+                  operations, before or after an existing entry.";
+             }
+             type data-resource-identifier;
+             description
+               "The absolute URL path for the data node that is being
+                used as the insertion point or move point for the
+                target of this edit entry.";
+           }
+
+           leaf where {
+             when "../operation = 'insert' or ../operation = 'move'" {
+               description
+                 "Where leaf only applies for insert or move
+                  operations.";
+             }
+             type enumeration {
+               enum before {
+                 description
+                   "Insert or move a data node before the data resource
+                    identified by the 'point' parameter.";
+               }
+               enum after {
+                 description
+                   "Insert or move a data node after the data resource
+                    identified by the 'point' parameter.";
+               }
+               enum first {
+                 description
+                   "Insert or move a data node so it becomes ordered
+                    as the first entry.";
+               }
+               enum last {
+                 description
+                   "Insert or move a data node so it becomes ordered
+                    as the last entry.";
+               }
+
+             }
+             default last;
+             description
+               "Identifies where a data resource will be inserted or
+                moved. YANG only allows these operations for
+                list and leaf-list data nodes that are ordered-by
+                user.";
+           }
+
+           anyxml value {
+             when "(../operation = 'create' or " +
+               "../operation = 'merge' " +
+               "or ../operation = 'replace' or " +
+               "../operation = 'insert')" {
+               description
+                 "Value node only used for create, merge,
+                  replace, and insert operations";
+             }
+             description
+               "Value used for this edit operation.";
+           }
+         }
+       }
+
+     } // grouping yang-patch
+
+
+     grouping yang-patch-status {
+
+       description
+         "A grouping that contains a YANG container
+          representing the syntax and semantics of
+          YANG Patch status response message.";
+
+       container yang-patch-status {
+         description
+           "A container representing the response message
+            sent by the server after a YANG Patch edit
+            request message has been processed.";
+
+         leaf patch-id {
+           type string;
+           description
+             "The patch-id value used in the request";
+         }
+
+         choice global-status {
+           description
+             "Report global errors or complete success.
+              If there is no case selected then errors
+              are reported in the edit-status container.";
+
+           case global-errors {
+             uses errors;
+             description
+               "This container will be present if global
+                errors unrelated to a specific edit occurred.";
+           }
+           leaf ok {
+             type empty;
+             description
+               "This leaf will be present if the request succeeded
+                and there are no errors reported in the edit-status
+                container.";
+           }
+         }
+
+         container edit-status {
+           description
+             "This container will be present if there are
+              edit-specific status responses to report.";
+
+           list edit {
+             key edit-id;
+
+             description
+               "Represents a list of status responses,
+                corresponding to edits in the YANG Patch
+                request message.  If an edit entry was
+                skipped or not reached by the server,
+                then this list will not contain a corresponding
+                entry for that edit.";
+
+             leaf edit-id {
+               type string;
+                description
+                  "Response status is for the edit list entry
+                   with this edit-id value.";
+             }
+             choice edit-status-choice {
+               description
+                 "A choice between different types of status
+                  responses for each edit entry.";
+               leaf ok {
+                 type empty;
+                 description
+                   "This edit entry was invoked without any
+                    errors detected by the server associated
+                    with this edit.";
+               }
+               leaf location {
+                 type inet:uri;
+                 description
+                   "Contains the Location header value that would be
+                    returned if this edit causes a new resource to be
+                    created. If the edit identified by the same edit-id
+                    value was successfully invoked and a new resource
+                    was created, then this field will be returned
+                    instead of 'ok'.";
+               }
+               case errors {
+                 uses errors;
+                 description
+                   "The server detected errors associated with the
+                     edit identified by the same edit-id value.";
+               }
+             }
+           }
+         }
+       }
+     }  // grouping yang-patch-status
+
+
+     grouping errors {
+
+       description
+         "A grouping that contains a YANG container
+          representing the syntax and semantics of a
+          YANG Patch errors report within a response message.";
+
+       container errors {
+         config false;  // needed so list error does not need a key
+         description
+           "Represents an error report returned by the server if
+            a request results in an error.";
+
+         list error {
+           description
+             "An entry containing information about one
+              specific error that occurred while processing
+              a RESTCONF request.";
+           reference "RFC 6241, Section 4.3";
+
+           leaf error-type {
+             type enumeration {
+               enum transport {
+                 description "The transport layer";
+               }
+               enum rpc {
+                 description "The rpc or notification layer";
+               }
+               enum protocol {
+                 description "The protocol operation layer";
+               }
+               enum application {
+                 description "The server application layer";
+               }
+             }
+             mandatory true;
+             description
+               "The protocol layer where the error occurred.";
+           }
+
+           leaf error-tag {
+             type string;
+             mandatory true;
+             description
+               "The enumerated error tag.";
+           }
+
+           leaf error-app-tag {
+             type string;
+             description
+               "The application-specific error tag.";
+           }
+
+           leaf error-path {
+             type data-resource-identifier;
+             description
+               "The target data resource identifier associated
+                with the error, if any.";
+           }
+           leaf error-message {
+             type string;
+             description
+               "A message describing the error.";
+           }
+
+           container error-info {
+              description
+                "A container allowing additional information
+                 to be included in the error report.";
+              // arbitrary anyxml content here
+           }
+         }
+       }
+     } // grouping errors
+
+     /** grouping restconf removed for testing purposes **/
+
+     grouping notification {
+       description
+         "Contains the notification message wrapper definition.";
+
+       container notification {
+         description
+           "RESTCONF notification message wrapper.";
+         leaf event-time {
+           type yang:date-and-time;
+           mandatory true;
+           description
+             "The time the event was generated by the
+              event source.";
+           reference
+             "RFC 5277, section 4, <eventTime> element.";
+         }
+
+         /* The YANG-specific notification container is encoded
+          * after the 'event-time' element.  The format
+          * corresponds to the notificationContent element
+          * in RFC 5277, section 4. For example:
+          *
+          *  module example-one {
+          *     ...
+          *     notification event1 { ... }
+          *
+          *  }
+          *
+          *  Encoded as element 'event1' in the namespace
+          *  for module 'example-one'.
+          */
+       }
+     }  // grouping notification
+
+   }
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/modules/restconf-module-testing/restconf-module-with-missing-list-module.yang b/restconf/sal-rest-connector/src/test/resources/modules/restconf-module-testing/restconf-module-with-missing-list-module.yang
new file mode 100644 (file)
index 0000000..34b4d48
--- /dev/null
@@ -0,0 +1,650 @@
+module restconf-module-with-missing-list-module {
+     namespace "urn:ietf:params:xml:ns:yang:ietf-restconf-rmwmlm";
+     prefix "restconf";
+
+     import ietf-yang-types { prefix yang; }
+     import ietf-inet-types { prefix inet; }
+
+     organization
+       "IETF NETCONF (Network Configuration) Working Group";
+
+     contact
+       "Editor:   Andy Bierman
+                  <mailto:andy@yumaworks.com>
+
+        Editor:   Martin Bjorklund
+                  <mailto:mbj@tail-f.com>
+
+        Editor:   Kent Watsen
+                  <mailto:kwatsen@juniper.net>
+
+        Editor:   Rex Fernando
+                  <mailto:rex@cisco.com>";
+
+     description
+       "This module contains conceptual YANG specifications
+        for the YANG Patch and error content that is used in
+        RESTCONF protocol messages. A conceptual container
+        representing the RESTCONF API nodes (media type
+        application/yang.api).
+
+        Note that the YANG definitions within this module do not
+        represent configuration data of any kind.
+        The YANG grouping statements provide a normative syntax
+        for XML and JSON message encoding purposes.
+        Copyright (c) 2013 IETF Trust and the persons identified as
+        authors of the code.  All rights reserved.
+
+        Redistribution and use in source and binary forms, with or
+        without modification, is permitted pursuant to, and subject
+        to the license terms contained in, the Simplified BSD License
+        set forth in Section 4.c of the IETF Trust's Legal Provisions
+        Relating to IETF Documents
+        (http://trustee.ietf.org/license-info).
+
+        This version of this YANG module is part of RFC XXXX; see
+        the RFC itself for full legal notices.";
+
+     // RFC Ed.: replace XXXX with actual RFC number and remove this
+     // note.
+
+     // RFC Ed.: remove this note
+     // Note: extracted from draft-bierman-netconf-restconf-02.txt
+
+     // RFC Ed.: update the date below with the date of RFC publication
+     // and remove this note.
+     revision 2013-10-19 {
+       description
+         "Initial revision.";
+       reference
+         "RFC XXXX: RESTCONF Protocol.";
+     }
+
+     typedef data-resource-identifier {
+       type string {
+         length "1 .. max";
+       }
+       description
+         "Contains a Data Resource Identifier formatted string
+          to identify a specific data node. The data node that
+          uses this data type SHOULD define the document root
+          for data resource identifiers.  The default document
+          root is the target datastore conceptual root node.
+          Data resource identifiers are defined relative to
+          this document root.";
+       reference
+         "RFC XXXX: [sec. 5.3.1.1 ABNF For Data Resource Identifiers]";
+     }
+
+     // this typedef is TBD; not currently used
+     typedef datastore-identifier {
+       type union {
+         type enumeration {
+           enum candidate {
+             description
+               "Identifies the NETCONF shared candidate datastore.";
+             reference
+               "RFC 6241, section 8.3";
+           }
+           enum running {
+             description
+               "Identifies the NETCONF running datastore.";
+             reference
+               "RFC 6241, section 5.1";
+           }
+           enum startup {
+             description
+               "Identifies the NETCONF startup datastore.";
+             reference
+               "RFC 6241, section 8.7";
+           }
+         }
+         type string;
+       }
+       description
+         "Contains a string to identify a specific datastore.
+          The enumerated datastore identifier values are
+          reserved for standard datastore names.";
+     }
+
+     typedef revision-identifier {
+       type string {
+         pattern '\d{4}-\d{2}-\d{2}';
+       }
+       description
+         "Represents a specific date in YYYY-MM-DD format.
+          TBD: make pattern more precise to exclude leading zeros.";
+     }
+
+     grouping yang-patch {
+       description
+         "A grouping that contains a YANG container
+          representing the syntax and semantics of a
+          YANG Patch edit request message.";
+
+       container yang-patch {
+         description
+           "Represents a conceptual sequence of datastore edits,
+            called a patch. Each patch is given a client-assigned
+            patch identifier. Each edit MUST be applied
+            in ascending order, and all edits MUST be applied.
+            If any errors occur, then the target datastore MUST NOT
+            be changed by the patch operation.
+
+            A patch MUST be validated by the server to be a
+            well-formed message before any of the patch edits
+            are validated or attempted.
+
+            YANG datastore validation (defined in RFC 6020, section
+            8.3.3) is performed after all edits have been
+            individually validated.
+
+            It is possible for a datastore constraint violation to occur
+            due to any node in the datastore, including nodes not
+            included in the edit list. Any validation errors MUST
+            be reported in the reply message.";
+
+         reference
+           "RFC 6020, section 8.3.";
+
+         leaf patch-id {
+           type string;
+           description
+             "An arbitrary string provided by the client to identify
+              the entire patch.  This value SHOULD be present in any
+              audit logging records generated by the server for the
+              patch. Error messages returned by the server pertaining
+              to this patch will be identified by this patch-id value.";
+         }
+
+         leaf comment {
+           type string {
+             length "0 .. 1024";
+           }
+           description
+             "An arbitrary string provided by the client to describe
+              the entire patch.  This value SHOULD be present in any
+              audit logging records generated by the server for the
+              patch.";
+         }
+
+         list edit {
+           key edit-id;
+           ordered-by user;
+
+           description
+             "Represents one edit within the YANG Patch
+              request message.";
+           leaf edit-id {
+             type string;
+             description
+               "Arbitrary string index for the edit.
+                Error messages returned by the server pertaining
+                to a specific edit will be identified by this
+                value.";
+           }
+
+           leaf operation {
+             type enumeration {
+               enum create {
+                 description
+                   "The target data node is created using the
+                    supplied value, only if it does not already
+                    exist.";
+               }
+               enum delete {
+                 description
+                   "Delete the target node, only if the data resource
+                    currently exists, otherwise return an error.";
+               }
+               enum insert {
+                 description
+                   "Insert the supplied value into a user-ordered
+                    list or leaf-list entry. The target node must
+                    represent a new data resource.";
+               }
+               enum merge {
+                 description
+                   "The supplied value is merged with the target data
+                    node.";
+               }
+               enum move {
+                 description
+                   "Move the target node. Reorder a user-ordered
+                    list or leaf-list. The target node must represent
+                    an existing data resource.";
+               }
+               enum replace {
+                 description
+                   "The supplied value is used to replace the target
+                    data node.";
+               }
+               enum remove {
+                 description
+                   "Delete the target node if it currently exists.";
+               }
+             }
+             mandatory true;
+             description
+               "The datastore operation requested for the associated
+                edit entry";
+           }
+
+           leaf target {
+             type data-resource-identifier;
+             mandatory true;
+             description
+               "Identifies the target data resource for the edit
+                operation.";
+           }
+
+           leaf point {
+             when "(../operation = 'insert' or " +
+               "../operation = 'move') and " +
+               "(../where = 'before' or ../where = 'after')" {
+               description
+                 "Point leaf only applies for insert or move
+                  operations, before or after an existing entry.";
+             }
+             type data-resource-identifier;
+             description
+               "The absolute URL path for the data node that is being
+                used as the insertion point or move point for the
+                target of this edit entry.";
+           }
+
+           leaf where {
+             when "../operation = 'insert' or ../operation = 'move'" {
+               description
+                 "Where leaf only applies for insert or move
+                  operations.";
+             }
+             type enumeration {
+               enum before {
+                 description
+                   "Insert or move a data node before the data resource
+                    identified by the 'point' parameter.";
+               }
+               enum after {
+                 description
+                   "Insert or move a data node after the data resource
+                    identified by the 'point' parameter.";
+               }
+               enum first {
+                 description
+                   "Insert or move a data node so it becomes ordered
+                    as the first entry.";
+               }
+               enum last {
+                 description
+                   "Insert or move a data node so it becomes ordered
+                    as the last entry.";
+               }
+
+             }
+             default last;
+             description
+               "Identifies where a data resource will be inserted or
+                moved. YANG only allows these operations for
+                list and leaf-list data nodes that are ordered-by
+                user.";
+           }
+
+           anyxml value {
+             when "(../operation = 'create' or " +
+               "../operation = 'merge' " +
+               "or ../operation = 'replace' or " +
+               "../operation = 'insert')" {
+               description
+                 "Value node only used for create, merge,
+                  replace, and insert operations";
+             }
+             description
+               "Value used for this edit operation.";
+           }
+         }
+       }
+
+     } // grouping yang-patch
+
+
+     grouping yang-patch-status {
+
+       description
+         "A grouping that contains a YANG container
+          representing the syntax and semantics of
+          YANG Patch status response message.";
+
+       container yang-patch-status {
+         description
+           "A container representing the response message
+            sent by the server after a YANG Patch edit
+            request message has been processed.";
+
+         leaf patch-id {
+           type string;
+           description
+             "The patch-id value used in the request";
+         }
+
+         choice global-status {
+           description
+             "Report global errors or complete success.
+              If there is no case selected then errors
+              are reported in the edit-status container.";
+
+           case global-errors {
+             uses errors;
+             description
+               "This container will be present if global
+                errors unrelated to a specific edit occurred.";
+           }
+           leaf ok {
+             type empty;
+             description
+               "This leaf will be present if the request succeeded
+                and there are no errors reported in the edit-status
+                container.";
+           }
+         }
+
+         container edit-status {
+           description
+             "This container will be present if there are
+              edit-specific status responses to report.";
+
+           list edit {
+             key edit-id;
+
+             description
+               "Represents a list of status responses,
+                corresponding to edits in the YANG Patch
+                request message.  If an edit entry was
+                skipped or not reached by the server,
+                then this list will not contain a corresponding
+                entry for that edit.";
+
+             leaf edit-id {
+               type string;
+                description
+                  "Response status is for the edit list entry
+                   with this edit-id value.";
+             }
+             choice edit-status-choice {
+               description
+                 "A choice between different types of status
+                  responses for each edit entry.";
+               leaf ok {
+                 type empty;
+                 description
+                   "This edit entry was invoked without any
+                    errors detected by the server associated
+                    with this edit.";
+               }
+               leaf location {
+                 type inet:uri;
+                 description
+                   "Contains the Location header value that would be
+                    returned if this edit causes a new resource to be
+                    created. If the edit identified by the same edit-id
+                    value was successfully invoked and a new resource
+                    was created, then this field will be returned
+                    instead of 'ok'.";
+               }
+               case errors {
+                 uses errors;
+                 description
+                   "The server detected errors associated with the
+                     edit identified by the same edit-id value.";
+               }
+             }
+           }
+         }
+       }
+     }  // grouping yang-patch-status
+
+
+     grouping errors {
+
+       description
+         "A grouping that contains a YANG container
+          representing the syntax and semantics of a
+          YANG Patch errors report within a response message.";
+
+       container errors {
+         config false;  // needed so list error does not need a key
+         description
+           "Represents an error report returned by the server if
+            a request results in an error.";
+
+         list error {
+           description
+             "An entry containing information about one
+              specific error that occurred while processing
+              a RESTCONF request.";
+           reference "RFC 6241, Section 4.3";
+
+           leaf error-type {
+             type enumeration {
+               enum transport {
+                 description "The transport layer";
+               }
+               enum rpc {
+                 description "The rpc or notification layer";
+               }
+               enum protocol {
+                 description "The protocol operation layer";
+               }
+               enum application {
+                 description "The server application layer";
+               }
+             }
+             mandatory true;
+             description
+               "The protocol layer where the error occurred.";
+           }
+
+           leaf error-tag {
+             type string;
+             mandatory true;
+             description
+               "The enumerated error tag.";
+           }
+
+           leaf error-app-tag {
+             type string;
+             description
+               "The application-specific error tag.";
+           }
+
+           leaf error-path {
+             type data-resource-identifier;
+             description
+               "The target data resource identifier associated
+                with the error, if any.";
+           }
+           leaf error-message {
+             type string;
+             description
+               "A message describing the error.";
+           }
+
+           container error-info {
+              description
+                "A container allowing additional information
+                 to be included in the error report.";
+              // arbitrary anyxml content here
+           }
+         }
+       }
+     } // grouping errors
+
+
+     grouping restconf {
+
+       description
+         "A grouping that contains a YANG container
+          representing the syntax and semantics of
+          the RESTCONF API resource.";
+
+       container restconf {
+         description
+           "Conceptual container representing the
+            application/yang.api resource type.";
+
+         container config {
+           description
+             "Container representing the application/yang.datastore
+              resource type. Represents the conceptual root of the
+              unified configuration datastore containing YANG data
+              nodes. The child nodes of this container are
+              configuration data resources (application/yang.data)
+              defined as top-level YANG data nodes from the modules
+              advertised by the server in /restconf/modules.";
+         }
+
+         container operational {
+           description
+             "Container representing the application/yang.datastore
+              resource type. Represents the conceptual root of the
+              operational data supported by the server.  The child
+              nodes of this container are operational data resources
+              (application/yang.data) defined as top-level
+              YANG data nodes from the modules advertised by
+              the server in /restconf/modules.";
+         }
+
+         container modules {
+           description
+             "Contains a list of module description entries.
+              These modules are currently loaded into the server.";
+
+            // removed list module for testing purposes + added list test-list
+            list test-list {
+              leaf test-leaf {
+                type string;
+              }
+            }
+         }
+
+         container operations {
+           description
+             "Container for all operation resources
+              (application/yang.operation),
+
+              Each resource is represented as an empty leaf with the
+              name of the RPC operation from the YANG rpc statement.
+
+              E.g.;
+
+                 POST /restconf/operations/show-log-errors
+
+                 leaf show-log-errors {
+                   type empty;
+                 }
+             ";
+         }
+
+         container streams {
+           description
+             "Container representing the notification event streams
+              supported by the server.";
+            reference
+              "RFC 5277, Section 3.4, <streams> element.";
+
+           list stream {
+             key name;
+             description
+               "Each entry describes an event stream supported by
+                the server.";
+
+             leaf name {
+               type string;
+               description "The stream name";
+               reference "RFC 5277, Section 3.4, <name> element.";
+             }
+
+             leaf description {
+               type string;
+               description "Description of stream content";
+               reference
+                 "RFC 5277, Section 3.4, <description> element.";
+             }
+
+             leaf replay-support {
+               type boolean;
+               description
+                 "Indicates if replay buffer supported for this stream";
+               reference
+                 "RFC 5277, Section 3.4, <replaySupport> element.";
+             }
+
+             leaf replay-log-creation-time {
+               type yang:date-and-time;
+               description
+                 "Indicates the time the replay log for this stream
+                  was created.";
+               reference
+                 "RFC 5277, Section 3.4, <replayLogCreationTime>
+                  element.";
+             }
+
+             leaf events {
+               type empty;
+               description
+                 "Represents the entry point for establishing
+                  notification delivery via server sent events.";
+             }
+           }
+         }
+
+         leaf version {
+           type enumeration {
+             enum "1.0" {
+               description
+                 "Version 1.0 of the RESTCONF protocol.";
+             }
+           }
+           config false;
+           description
+             "Contains the RESTCONF protocol version.";
+         }
+       }
+     }  // grouping restconf
+
+
+     grouping notification {
+       description
+         "Contains the notification message wrapper definition.";
+
+       container notification {
+         description
+           "RESTCONF notification message wrapper.";
+         leaf event-time {
+           type yang:date-and-time;
+           mandatory true;
+           description
+             "The time the event was generated by the
+              event source.";
+           reference
+             "RFC 5277, section 4, <eventTime> element.";
+         }
+
+         /* The YANG-specific notification container is encoded
+          * after the 'event-time' element.  The format
+          * corresponds to the notificationContent element
+          * in RFC 5277, section 4. For example:
+          *
+          *  module example-one {
+          *     ...
+          *     notification event1 { ... }
+          *
+          *  }
+          *
+          *  Encoded as element 'event1' in the namespace
+          *  for module 'example-one'.
+          */
+       }
+     }  // grouping notification
+
+   }
\ No newline at end of file
index fcc7e308a1e9d8a629e7a8896893fae44805cdf7..ba8c9aa1d7f7f8e6d054053c6a0e789f588faa5f 100644 (file)
@@ -586,7 +586,12 @@ module restconf-module-with-missing-list-stream {
             reference
               "RFC 5277, Section 3.4, <streams> element.";
 
-           /** deleted list stream for testing purposes **/
+           /** deleted list stream for testing purposes + added list test-list **/
+           list test-list {
+             leaf test-leaf {
+               type string;
+             }
+           }
          }
 
          leaf version {
diff --git a/restconf/sal-rest-connector/src/test/resources/parser-identifier/mount-point.yang b/restconf/sal-rest-connector/src/test/resources/parser-identifier/mount-point.yang
new file mode 100644 (file)
index 0000000..02a468f
--- /dev/null
@@ -0,0 +1,17 @@
+module mount-point {
+  namespace "mount:point";
+  prefix "mountp";
+  yang-version 1;
+
+  import parser-identifier-included { prefix pii; revision-date 2016-06-02; }
+
+  revision 2016-06-02 {
+    description "Initial revision.";
+  }
+
+  container mount-container {
+    leaf point-number {
+      type uint8;
+    }
+  }
+}
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/parser-identifier/parser-identifier-test-included.yang b/restconf/sal-rest-connector/src/test/resources/parser-identifier/parser-identifier-test-included.yang
new file mode 100644 (file)
index 0000000..d5200a9
--- /dev/null
@@ -0,0 +1,23 @@
+module parser-identifier-included {
+  namespace "parser:identifier:included";
+  prefix "parseridinc";
+  yang-version 1;
+
+  revision 2016-06-02 {
+    description
+      "Initial revision.";
+  }
+
+  list list-1 {
+    key "name revision";
+    description "List in grouping";
+
+    leaf name {
+      type string;
+    }
+
+    leaf revision {
+      type string;
+    }
+  }
+}
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/parser-identifier/parser-identifier-test.yang b/restconf/sal-rest-connector/src/test/resources/parser-identifier/parser-identifier-test.yang
new file mode 100644 (file)
index 0000000..ac91dc1
--- /dev/null
@@ -0,0 +1,40 @@
+module parser-identifier {
+  namespace "parser:identifier";
+  prefix "parserid";
+  yang-version 1;
+
+  import parser-identifier-included { prefix pii; revision-date 2016-06-02; }
+
+  revision 2016-06-02 {
+    description
+      "Initial revision.";
+  }
+
+  container cont1 {
+    container cont2 {
+      list listTest {
+        uses group;
+      }
+    }
+  }
+
+  grouping group {
+    list list-in-grouping {
+      key name;
+
+      leaf name {
+        type string;
+      }
+
+      leaf leaf-A.B {
+        type uint8;
+      }
+    }
+  }
+
+  augment "/pii:list-1" {
+    leaf augment-leaf {
+      type string;
+    }
+  }
+}
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/parser-identifier/test-module.yang b/restconf/sal-rest-connector/src/test/resources/parser-identifier/test-module.yang
new file mode 100644 (file)
index 0000000..a9a6756
--- /dev/null
@@ -0,0 +1,9 @@
+module test-module {
+  namespace "test:module";
+  prefix "testm";
+  yang-version 1;
+
+  revision 2016-06-02 {
+    description "Initial revision.";
+  }
+}
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/restconf/parser/deserializer/deserializer-test-included.yang b/restconf/sal-rest-connector/src/test/resources/restconf/parser/deserializer/deserializer-test-included.yang
new file mode 100644 (file)
index 0000000..b46898c
--- /dev/null
@@ -0,0 +1,22 @@
+module deserializer-test-included {
+  namespace "deserializer:test:included";
+  prefix "dti";
+  yang-version 1;
+
+  revision 2016-06-06 {
+    description
+      "Initial revision.";
+  }
+
+  list augmented-list {
+    key list-key;
+
+    leaf list-key {
+      type uint16;
+    }
+
+    leaf list-value {
+      type string;
+    }
+  }
+}
diff --git a/restconf/sal-rest-connector/src/test/resources/restconf/parser/deserializer/deserializer-test.yang b/restconf/sal-rest-connector/src/test/resources/restconf/parser/deserializer/deserializer-test.yang
new file mode 100644 (file)
index 0000000..38f989e
--- /dev/null
@@ -0,0 +1,90 @@
+module deserializer-test {
+  namespace "deserializer:test";
+  prefix "dt";
+  yang-version 1;
+
+  import deserializer-test-included { prefix dti; revision-date 2016-06-06; }
+
+  revision 2016-06-06 {
+    description
+      "Initial revision.";
+  }
+
+  container contA {
+    leaf-list leaf-list-A {
+      type string;
+    }
+
+    leaf leaf-A {
+      type string;
+    }
+
+    list list-A {
+      key list-key;
+
+      leaf list-key {
+        type uint8;
+      }
+
+      leaf-list leaf-list-AA {
+        type string;
+      }
+    }
+  }
+
+  leaf-list leaf-list-0 {
+    type boolean;
+  }
+
+  leaf leaf-0 {
+    type string;
+  }
+
+  list list-no-key {
+    leaf name {
+      type string;
+    }
+
+    leaf number {
+      type uint8;
+    }
+  }
+
+  list list-one-key {
+    key name;
+
+    leaf name {
+      type string;
+    }
+
+    leaf number {
+      type uint8;
+    }
+  }
+
+  list list-multiple-keys {
+    key "name number enabled";
+
+    leaf name {
+      type string;
+    }
+
+    leaf number {
+      type uint8;
+    }
+
+    leaf enabled {
+      type boolean;
+    }
+
+    leaf string-value {
+      type string;
+    }
+  }
+
+  augment "/dti:augmented-list" {
+    leaf augmented-leaf {
+      type string;
+    }
+  }
+}
index ec5a7d2731debe77a7d52b86ac4404c29c5711a8..981e6fe99504eadc6ccbb9ee2bce8d58a0a83750 100644 (file)
@@ -27,7 +27,7 @@ module list-test {
                 leaf key5 {
                    type string;
                 }
-                leaf result{
+                leaf result {
                     type string;
                 }
            }
diff --git a/restconf/sal-rest-connector/src/test/resources/restconf/parser/serializer/serializer-test-included.yang b/restconf/sal-rest-connector/src/test/resources/restconf/parser/serializer/serializer-test-included.yang
new file mode 100644 (file)
index 0000000..c404aeb
--- /dev/null
@@ -0,0 +1,22 @@
+module serializer-test-included {
+  namespace "serializer:test:included";
+  prefix "sti";
+  yang-version 1;
+
+  revision 2016-06-06 {
+    description
+      "Initial revision.";
+  }
+
+  list augmented-list {
+    key list-key;
+
+    leaf list-key {
+      type uint16;
+    }
+
+    leaf list-value {
+      type string;
+    }
+  }
+}
\ No newline at end of file
diff --git a/restconf/sal-rest-connector/src/test/resources/restconf/parser/serializer/serializer-test.yang b/restconf/sal-rest-connector/src/test/resources/restconf/parser/serializer/serializer-test.yang
new file mode 100644 (file)
index 0000000..691e4dc
--- /dev/null
@@ -0,0 +1,86 @@
+module serializer-test {
+  namespace "serializer:test";
+  prefix "st";
+  yang-version 1;
+
+  import serializer-test-included { prefix sti; revision-date 2016-06-06; }
+
+  revision 2016-06-06 {
+    description
+      "Initial revision.";
+  }
+
+  container contA {
+    leaf-list leaf-list-A {
+      type string;
+    }
+
+    leaf leaf-A {
+      type string;
+    }
+
+    list list-A {
+      key list-key;
+
+      leaf list-key {
+        type uint8;
+      }
+
+      leaf-list leaf-list-AA {
+        type string;
+      }
+    }
+  }
+
+  leaf-list leaf-list-0 {
+    type boolean;
+  }
+
+  leaf leaf-0 {
+    type string;
+  }
+
+  list list-no-key {
+    leaf name {
+      type string;
+    }
+
+    leaf number {
+      type uint8;
+    }
+  }
+
+  list list-one-key {
+    key name;
+
+    leaf name {
+      type string;
+    }
+
+    leaf number {
+      type uint8;
+    }
+  }
+
+  list list-multiple-keys {
+    key "name number enabled";
+
+    leaf name {
+      type string;
+    }
+
+    leaf number {
+      type uint8;
+    }
+
+    leaf enabled {
+      type boolean;
+    }
+  }
+
+  augment "/sti:augmented-list" {
+    leaf augmented-leaf {
+      type string;
+    }
+  }
+}
\ No newline at end of file