BUG 2282 - JSON top level element without module name 59/12459/2
authorJozef Gloncak <jgloncak@cisco.com>
Mon, 3 Nov 2014 15:02:54 +0000 (16:02 +0100)
committerJozef Gloncak <jgloncak@cisco.com>
Tue, 4 Nov 2014 06:38:33 +0000 (07:38 +0100)
Json top level element now doesn't have to contain module name prefix.

If namespace of some child element was changed and only one child with
specified name exist in schema then its namespace is used by default.

If more then one potential child in various namespaces exists then
exception is raised. Exception is also raised if no such element as
specified in JSON input exists in YANG schema.

Change-Id: I8a38fde4f2e9b79562f6bf7c79acafd51c7bff32
Signed-off-by: Jozef Gloncak <jgloncak@cisco.com>
yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JsonParserStream.java
yang/yang-data-codec-gson/src/test/java/org/opendaylight/yangtools/yang/data/codec/gson/JsonStreamToNormalizedNodeTest.java
yang/yang-data-codec-gson/src/test/java/org/opendaylight/yangtools/yang/data/codec/gson/TestingNormalizedNodeStructuresCreator.java
yang/yang-data-codec-gson/src/test/resources/complexjson/missing-module-in-top-level.json [new file with mode: 0644]
yang/yang-data-codec-gson/src/test/resources/complexjson/namesakes.json [new file with mode: 0644]
yang/yang-data-codec-gson/src/test/resources/complexjson/not-existing-element.json [new file with mode: 0644]
yang/yang-data-codec-gson/src/test/resources/complexjson/unkeyed-node-in-container.json
yang/yang-data-codec-gson/src/test/resources/complexjson/yang/complexjson-augmentation-namesake.yang [new file with mode: 0644]
yang/yang-data-codec-gson/src/test/resources/complexjson/yang/complexjson-augmentation.yang

index 93946567dcb9080e98d131c9922afc3acdd220a5..724c85cff1dcbda40d3fbec38f5392edbc1db8de 100644 (file)
@@ -8,7 +8,6 @@
 package org.opendaylight.yangtools.yang.data.codec.gson;
 
 import com.google.common.annotations.Beta;
-import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.gson.JsonIOException;
 import com.google.gson.JsonParseException;
@@ -24,6 +23,7 @@ import java.net.URI;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Deque;
 import java.util.HashSet;
 import java.util.List;
@@ -146,7 +146,7 @@ public final class JsonParserStream implements Closeable, Flushable {
             in.beginObject();
             while (in.hasNext()) {
                 final String jsonElementName = in.nextName();
-                final NamespaceAndName namespaceAndName = resolveNamespace(jsonElementName);
+                final NamespaceAndName namespaceAndName = resolveNamespace(jsonElementName, parent.getSchema());
                 final String localName = namespaceAndName.getName();
                 addNamespace(namespaceAndName.getUri());
                 if (namesakes.contains(jsonElementName)) {
@@ -213,19 +213,11 @@ public final class JsonParserStream implements Closeable, Flushable {
         namespaces.pop();
     }
 
-    private void addNamespace(final Optional<URI> namespace) {
-        if (!namespace.isPresent()) {
-            if (namespaces.isEmpty()) {
-                throw new IllegalStateException("Namespace has to be specified at top level.");
-            } else {
-                namespaces.push(namespaces.peek());
-            }
-        } else {
-            namespaces.push(namespace.get());
-        }
+    private void addNamespace(final URI namespace) {
+        namespaces.push(namespace);
     }
 
-    private NamespaceAndName resolveNamespace(final String childName) {
+    private NamespaceAndName resolveNamespace(final String childName, final DataSchemaNode dataSchemaNode) {
         int lastIndexOfColon = childName.lastIndexOf(':');
         String moduleNamePart = null;
         String nodeNamePart = null;
@@ -240,8 +232,52 @@ public final class JsonParserStream implements Closeable, Flushable {
             nodeNamePart = childName;
         }
 
-        Optional<URI> namespaceOpt = namespace == null ? Optional.<URI> absent() : Optional.of(namespace);
-        return new NamespaceAndName(nodeNamePart, namespaceOpt);
+        if (namespace == null) {
+            Set<URI> potentialUris = Collections.emptySet();
+            potentialUris = resolveAllPotentialNamespaces(nodeNamePart, dataSchemaNode);
+            if (potentialUris.contains(getCurrentNamespace())) {
+                namespace = getCurrentNamespace();
+            } else if (potentialUris.size() == 1) {
+                namespace = potentialUris.iterator().next();
+            } else if (potentialUris.size() > 1) {
+                throw new IllegalStateException("Choose suitable module name for element "+nodeNamePart+":"+toModuleNames(potentialUris));
+            } else if (potentialUris.isEmpty()) {
+                throw new IllegalStateException("Schema node with name "+nodeNamePart+" wasn't found.");
+            }
+        }
+
+        return new NamespaceAndName(nodeNamePart, namespace);
+    }
+
+    private String toModuleNames(Set<URI> potentialUris) {
+        final StringBuilder builder = new StringBuilder();
+        for (URI potentialUri : potentialUris) {
+            builder.append("\n");
+            //FIXME how to get information about revision from JSON input? currently first available is used.
+            builder.append(schema.findModuleByNamespace(potentialUri).iterator().next().getName());
+        }
+        return builder.toString();
+    }
+
+    private Set<URI> resolveAllPotentialNamespaces(final String elementName, final DataSchemaNode dataSchemaNode) {
+        final Set<URI> potentialUris = new HashSet<>();
+        final Set<ChoiceNode> choices = new HashSet<>();
+        if (dataSchemaNode instanceof DataNodeContainer) {
+            for (DataSchemaNode childSchemaNode : ((DataNodeContainer) dataSchemaNode).getChildNodes()) {
+                if (childSchemaNode instanceof ChoiceNode) {
+                    choices.add((ChoiceNode)childSchemaNode);
+                } else if (childSchemaNode.getQName().getLocalName().equals(elementName)) {
+                    potentialUris.add(childSchemaNode.getQName().getNamespace());
+                }
+            }
+
+            for (ChoiceNode choiceNode : choices) {
+                for (ChoiceCaseNode concreteCase : choiceNode.getCases()) {
+                    potentialUris.addAll(resolveAllPotentialNamespaces(elementName, concreteCase));
+                }
+            }
+        }
+        return potentialUris;
     }
 
     private URI getCurrentNamespace() {
@@ -292,10 +328,10 @@ public final class JsonParserStream implements Closeable, Flushable {
     }
 
     private static class NamespaceAndName {
-        private final Optional<URI> uri;
+        private final URI uri;
         private final String name;
 
-        public NamespaceAndName(final String name, final Optional<URI> uri) {
+        public NamespaceAndName(final String name, final URI uri) {
             this.name = name;
             this.uri = uri;
         }
@@ -304,7 +340,7 @@ public final class JsonParserStream implements Closeable, Flushable {
             return name;
         }
 
-        public Optional<URI> getUri() {
+        public URI getUri() {
             return uri;
         }
     }
index 56d12104bd8b86e27888762767bca7f4b5b88d81..1a71142be5553ecee451b42edb5fce7d02bf2612 100644 (file)
@@ -8,6 +8,7 @@
 package org.opendaylight.yangtools.yang.data.codec.gson;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.opendaylight.yangtools.yang.data.codec.gson.TestUtils.loadModules;
 import static org.opendaylight.yangtools.yang.data.codec.gson.TestUtils.loadTextFile;
 
@@ -133,6 +134,57 @@ public class JsonStreamToNormalizedNodeTest {
         verifyTransformationToNormalizedNode(inputJson, TestingNormalizedNodeStructuresCreator.unkeyedNodeInContainer());
     }
 
+    /**
+     * Top level JSON element contains no information about module name.
+     *
+     * It should be possible to find out potential module name from available schema context.
+     *
+     */
+    @Test
+    public void missingModuleInfoInTopLevelElement() throws IOException, URISyntaxException {
+        String inputJson = loadTextFile("/complexjson/missing-module-in-top-level.json");
+        verifyTransformationToNormalizedNode(inputJson, TestingNormalizedNodeStructuresCreator.topLevelContainer());
+    }
+
+    /**
+     *
+     * Exception expected.
+     *
+     * It tests case when several elements with the same name and various namespaces exists and are in JSON specified
+     * without module name prefix.
+     */
+    @Test
+    public void leafNamesakes() throws IOException, URISyntaxException {
+        String inputJson = loadTextFile("/complexjson/namesakes.json");
+        try {
+            //second parameter isn't necessary because error will be raised before it is used.
+            verifyTransformationToNormalizedNode(inputJson, null);
+        } catch (IllegalStateException e) {
+            final String errorMessage = e.getMessage();
+            assertTrue(errorMessage.contains("Choose suitable module name for element lf11-namesake:"));
+            assertTrue(errorMessage.contains("complexjson-augmentation"));
+            assertTrue(errorMessage.contains("complexjson-augmentation-namesake"));
+        }
+    }
+
+    /**
+     *
+     * Exception expected.
+     *
+     * Json input contains element which doesn't exist in YANG schema
+     */
+    @Test
+    public void parsingNotExistingElement() throws IOException, URISyntaxException {
+        String inputJson = loadTextFile("/complexjson/not-existing-element.json");
+        try {
+            //second parameter isn't necessary because error will be raised before it is used.
+            verifyTransformationToNormalizedNode(inputJson, null);
+        } catch (IllegalStateException e) {
+            assertTrue(e.getMessage().contains("Schema node with name dummy-element wasn't found."));
+        }
+    }
+
+
     private void verifyTransformationToNormalizedNode(final String inputJson,
             final NormalizedNode<?, ?> awaitedStructure) {
         NormalizedNodeResult result = new NormalizedNodeResult();
index 1038ce883db6d8389f6ed15a77cc243b05011e0a..8898cee98158d17df767e405a14d43ef1ea7818a 100644 (file)
@@ -314,4 +314,7 @@ public class TestingNormalizedNodeStructuresCreator {
         return cont1Node(lst12Node());
     }
 
+    public static NormalizedNode<?, ?> topLevelContainer() {
+        return cont1Node();
+    }
 }
diff --git a/yang/yang-data-codec-gson/src/test/resources/complexjson/missing-module-in-top-level.json b/yang/yang-data-codec-gson/src/test/resources/complexjson/missing-module-in-top-level.json
new file mode 100644 (file)
index 0000000..1070c36
--- /dev/null
@@ -0,0 +1,4 @@
+{
+    "cont1": {
+    }
+}
diff --git a/yang/yang-data-codec-gson/src/test/resources/complexjson/namesakes.json b/yang/yang-data-codec-gson/src/test/resources/complexjson/namesakes.json
new file mode 100644 (file)
index 0000000..9c0fab4
--- /dev/null
@@ -0,0 +1,5 @@
+{
+    "cont1": {
+        "lf11-namesake":"value lf11"
+    }
+}
\ No newline at end of file
diff --git a/yang/yang-data-codec-gson/src/test/resources/complexjson/not-existing-element.json b/yang/yang-data-codec-gson/src/test/resources/complexjson/not-existing-element.json
new file mode 100644 (file)
index 0000000..ebc44bb
--- /dev/null
@@ -0,0 +1,5 @@
+{
+    "cont1": {
+        "dummy-element":"value lf11"
+    }
+}
\ No newline at end of file
diff --git a/yang/yang-data-codec-gson/src/test/resources/complexjson/yang/complexjson-augmentation-namesake.yang b/yang/yang-data-codec-gson/src/test/resources/complexjson/yang/complexjson-augmentation-namesake.yang
new file mode 100644 (file)
index 0000000..e94acb2
--- /dev/null
@@ -0,0 +1,18 @@
+module complexjson-augmentation-namesake {
+    namespace "ns:complex:json:augmentation:namesake";
+    prefix cjaugnmsk;
+
+  import complexjson {
+    prefix cj;
+  }
+
+    revision "2014-08-14" {
+    }
+
+    augment "/cj:cont1" {
+        leaf lf11-namesake {
+            type string;
+        }
+    }
+
+}
index d4ea623b19098c9d073b2d00012839ad99f16113..a4cf5adeb52738569597e4aa6614ea2ff8e5a12b 100644 (file)
@@ -1,24 +1,30 @@
 module complexjson-augmentation {
     namespace "ns:complex:json:augmentation";
     prefix cjaug;
-    
+
   import complexjson {
     prefix cj;
-  }    
+  }
 
-    revision "2014-08-14" {        
+    revision "2014-08-14" {
     }
-    
+
+    augment "/cj:cont1" {
+        leaf lf11-namesake {
+            type string;
+        }
+    }
+
     augment "/cj:cont1/cj:choc11/cj:c11A" {
-        leaf lf15_11  {
+        leaf lf15_11 {
                     type string;
                 }
-        leaf lf15_12  {
+        leaf lf15_12 {
                     type string;
                 }
-                
-    }    
-    
+
+    }
+
     augment "/cj:cont1" {
         leaf lf12_1aug {
                     type string;
@@ -26,12 +32,12 @@ module complexjson-augmentation {
         leaf lf12_2aug {
             type string;
         }
-    }    
-    
+    }
+
     augment "/cj:cont1/cj:choc11/cj:c11A" {
         leaf lf15_21aug {
                     type string;
                 }
-    }    
+    }
 
 }