BUG 2282 - JSON top level element without module name 68/12668/9
authorJozef Gloncak <jgloncak@cisco.com>
Mon, 3 Nov 2014 15:02:54 +0000 (16:02 +0100)
committerTony Tkacik <ttkacik@cisco.com>
Tue, 10 Mar 2015 18:26:07 +0000 (19:26 +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>
(cherry picked from commit 6eee2a1ce9e69a7b653a7b4427573eff67588d1f)

yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JsonParserStream.java
yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/RpcAsContainer.java [new file with mode: 0644]
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 1721f428d5c276edd109dd1cec93b29bb6d2ae15..4f832a8dbd106dccaab4f45e5859d900c44df234 100644 (file)
@@ -8,14 +8,12 @@
 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;
 import com.google.gson.JsonSyntaxException;
 import com.google.gson.stream.JsonReader;
 import com.google.gson.stream.MalformedJsonException;
-
 import java.io.Closeable;
 import java.io.EOFException;
 import java.io.Flushable;
@@ -24,11 +22,11 @@ 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;
 import java.util.Set;
-
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
@@ -39,6 +37,7 @@ import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
@@ -63,6 +62,9 @@ public final class JsonParserStream implements Closeable, Flushable {
     }
 
     public static JsonParserStream create(final NormalizedNodeStreamWriter writer, final SchemaContext schemaContext, final SchemaNode parentNode ) {
+        if(parentNode instanceof RpcDefinition) {
+            return new JsonParserStream(writer, schemaContext, new RpcAsContainer((RpcDefinition) parentNode));
+        }
         Preconditions.checkArgument(parentNode instanceof DataSchemaNode, "Instance of DataSchemaNode class awaited.");
         return new JsonParserStream(writer, schemaContext, (DataSchemaNode) parentNode);
     }
@@ -74,30 +76,30 @@ public final class JsonParserStream implements Closeable, Flushable {
     public JsonParserStream parse(final JsonReader reader) throws JsonIOException, JsonSyntaxException {
         // code copied from gson's JsonParser and Stream classes
 
-        boolean lenient = reader.isLenient();
+        final boolean lenient = reader.isLenient();
         reader.setLenient(true);
         boolean isEmpty = true;
         try {
             reader.peek();
             isEmpty = false;
-            CompositeNodeDataWithSchema compositeNodeDataWithSchema = new CompositeNodeDataWithSchema(parentNode);
+            final CompositeNodeDataWithSchema compositeNodeDataWithSchema = new CompositeNodeDataWithSchema(parentNode);
             read(reader, compositeNodeDataWithSchema);
             compositeNodeDataWithSchema.write(writer);
 
             return this;
             // return read(reader);
-        } catch (EOFException e) {
+        } catch (final EOFException e) {
             if (isEmpty) {
                 return this;
                 // return JsonNull.INSTANCE;
             }
             // The stream ended prematurely so it is likely a syntax error.
             throw new JsonSyntaxException(e);
-        } catch (MalformedJsonException e) {
+        } catch (final MalformedJsonException e) {
             throw new JsonSyntaxException(e);
-        } catch (IOException e) {
+        } catch (final IOException e) {
             throw new JsonIOException(e);
-        } catch (NumberFormatException e) {
+        } catch (final NumberFormatException e) {
             throw new JsonSyntaxException(e);
         } catch (StackOverflowError | OutOfMemoryError e) {
             throw new JsonParseException("Failed parsing JSON source: " + reader + " to Json", e);
@@ -142,11 +144,11 @@ public final class JsonParserStream implements Closeable, Flushable {
             in.endArray();
             return;
         case BEGIN_OBJECT:
-            Set<String> namesakes = new HashSet<>();
+            final Set<String> namesakes = new HashSet<>();
             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,20 +215,12 @@ 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) {
-        int lastIndexOfColon = childName.lastIndexOf(':');
+    private NamespaceAndName resolveNamespace(final String childName, final DataSchemaNode dataSchemaNode) {
+        final int lastIndexOfColon = childName.lastIndexOf(':');
         String moduleNamePart = null;
         String nodeNamePart = null;
         URI namespace = null;
@@ -240,8 +234,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(final Set<URI> potentialUris) {
+        final StringBuilder builder = new StringBuilder();
+        for (final 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 (final 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 (final ChoiceNode choiceNode : choices) {
+                for (final ChoiceCaseNode concreteCase : choiceNode.getCases()) {
+                    potentialUris.addAll(resolveAllPotentialNamespaces(elementName, concreteCase));
+                }
+            }
+        }
+        return potentialUris;
     }
 
     private URI getCurrentNamespace() {
@@ -262,9 +300,9 @@ public final class JsonParserStream implements Closeable, Flushable {
     private Deque<DataSchemaNode> findSchemaNodeByNameAndNamespace(final DataSchemaNode dataSchemaNode,
             final String childName, final URI namespace) {
         final Deque<DataSchemaNode> result = new ArrayDeque<>();
-        List<ChoiceNode> childChoices = new ArrayList<>();
+        final List<ChoiceNode> childChoices = new ArrayList<>();
         if (dataSchemaNode instanceof DataNodeContainer) {
-            for (DataSchemaNode childNode : ((DataNodeContainer) dataSchemaNode).getChildNodes()) {
+            for (final DataSchemaNode childNode : ((DataNodeContainer) dataSchemaNode).getChildNodes()) {
                 if (childNode instanceof ChoiceNode) {
                     childChoices.add((ChoiceNode) childNode);
                 } else {
@@ -277,9 +315,9 @@ public final class JsonParserStream implements Closeable, Flushable {
             }
         }
         // try to find data schema node in choice (looking for first match)
-        for (ChoiceNode choiceNode : childChoices) {
-            for (ChoiceCaseNode concreteCase : choiceNode.getCases()) {
-                Deque<DataSchemaNode> resultFromRecursion = findSchemaNodeByNameAndNamespace(concreteCase, childName,
+        for (final ChoiceNode choiceNode : childChoices) {
+            for (final ChoiceCaseNode concreteCase : choiceNode.getCases()) {
+                final Deque<DataSchemaNode> resultFromRecursion = findSchemaNodeByNameAndNamespace(concreteCase, childName,
                         namespace);
                 if (!resultFromRecursion.isEmpty()) {
                     resultFromRecursion.push(concreteCase);
@@ -292,10 +330,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 +342,7 @@ public final class JsonParserStream implements Closeable, Flushable {
             return name;
         }
 
-        public Optional<URI> getUri() {
+        public URI getUri() {
             return uri;
         }
     }
diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/RpcAsContainer.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/RpcAsContainer.java
new file mode 100644 (file)
index 0000000..0b64b22
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+ * Copyright (c) 2015 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.yangtools.yang.data.codec.gson;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
+import org.opendaylight.yangtools.yang.model.api.ConstraintDefinition;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.GroupingDefinition;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.opendaylight.yangtools.yang.model.api.Status;
+import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.UsesNode;
+
+final class RpcAsContainer implements ContainerSchemaNode {
+
+    private final RpcDefinition delegate;
+
+    @Override
+    public String getDescription() {
+        return delegate.getDescription();
+    }
+
+    @Override
+    public String getReference() {
+        return delegate.getReference();
+    }
+
+    @Override
+    public Set<TypeDefinition<?>> getTypeDefinitions() {
+        return delegate.getTypeDefinitions();
+    }
+
+    @Override
+    public Set<GroupingDefinition> getGroupings() {
+        return delegate.getGroupings();
+    }
+
+    @Override
+    public Status getStatus() {
+        return delegate.getStatus();
+    }
+
+    public ContainerSchemaNode getInput() {
+        return delegate.getInput();
+    }
+
+    public ContainerSchemaNode getOutput() {
+        return delegate.getOutput();
+    }
+
+    RpcAsContainer(final RpcDefinition parentNode) {
+        delegate = parentNode;
+    }
+
+    @Override
+    public QName getQName() {
+        return delegate.getQName();
+    }
+
+    @Override
+    public SchemaPath getPath() {
+        return delegate.getPath();
+    }
+
+    @Override
+    public List<UnknownSchemaNode> getUnknownSchemaNodes() {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public DataSchemaNode getDataChildByName(final QName name) {
+        return getDataChildByName(name.getLocalName());
+    }
+
+    @Override
+    public DataSchemaNode getDataChildByName(final String name) {
+        switch (name) {
+            case "input":
+                return delegate.getInput();
+            case "output":
+                return delegate.getOutput();
+        }
+        return null;
+    }
+
+    @Override
+    public Set<UsesNode> getUses() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public Set<AugmentationSchema> getAvailableAugmentations() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public boolean isPresenceContainer() {
+        return false;
+    }
+
+    @Override
+    public Collection<DataSchemaNode> getChildNodes() {
+        final ArrayList<DataSchemaNode> ret = new ArrayList<>();
+        final ContainerSchemaNode input = getInput();
+        final ContainerSchemaNode output = getOutput();
+        if(input != null) {
+            ret.add(input);
+        }
+        if(output != null) {
+            ret.add(output);
+        }
+        return ret;
+    }
+
+    @Override
+    public boolean isAugmenting() {
+        return false;
+    }
+
+    @Override
+    public boolean isAddedByUses() {
+        return false;
+    }
+
+    @Override
+    public boolean isConfiguration() {
+        return false;
+    }
+
+    @Override
+    public ConstraintDefinition getConstraints() {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+}
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 140c7ff008076384217f315a7956bfcc927f78e4..941c55a104fd1578b60c4da731d935a7a6ad5a92 100644 (file)
@@ -313,4 +313,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;
                 }
-    }    
+    }
 
 }