Choice and case resolving in JSON output 06/3206/2
authorJozef Gloncak <jgloncak@cisco.com>
Thu, 28 Nov 2013 14:00:45 +0000 (15:00 +0100)
committerJozef Gloncak <jgloncak@cisco.com>
Fri, 29 Nov 2013 06:45:38 +0000 (07:45 +0100)
Searching via choices and cases which are defined in YANG when resolving
composite node to find elements with corresponding name.

Change-Id: I4b62b323676a2cdd8212f102bcd0dbbbaf9b6c10
Signed-off-by: Jozef Gloncak <jgloncak@cisco.com>
12 files changed:
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonMapper.java
opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/SchemaLocation.java [new file with mode: 0644]
opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/TestUtils.java
opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/ToJsonChoiceCaseTest.java [new file with mode: 0644]
opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/choice.yang [new file with mode: 0644]
opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/xml/data_container.xml [new file with mode: 0644]
opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/xml/data_leaflist.xml [new file with mode: 0644]
opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/xml/data_list.xml [new file with mode: 0644]
opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/xml/data_more_choices_same_level.xml [new file with mode: 0644]
opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/xml/data_no_first_case.xml [new file with mode: 0644]
opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/xml/data_random_level.xml [new file with mode: 0644]
opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/xml/data_various_path.xml [new file with mode: 0644]

index 351ae6ebbee085614943d7608ec51669a204f1ff..36b46a171cde4706a1b7e22d17ed4c7e00ccdc90 100644 (file)
@@ -47,13 +47,27 @@ class JsonMapper {
         checkNotNull(parent);
         checkNotNull(parentSchema);
 
+        List<String> longestPathToElementViaChoiceCase = new ArrayList<>();
         for (Node<?> child : parent.getChildren()) {
-            DataSchemaNode childSchema = findFirstSchemaForNode(child, parentSchema.getChildNodes());
-            if (childSchema == null) {
-                throw new UnsupportedDataTypeException("Probably the data node \"" + child.getNodeType().getLocalName()
-                        + "\" is not conform to schema");
+            Deque<String> choiceCasePathStack = new ArrayDeque<>(longestPathToElementViaChoiceCase);
+            SchemaLocation schemaLocation = findFirstSchemaForNode(child, parentSchema.getChildNodes(),
+                    choiceCasePathStack);
+
+            if (schemaLocation == null) {
+                if (!choiceCasePathStack.isEmpty()) {
+                    throw new UnsupportedDataTypeException("On choice-case path " + choiceCasePathStack
+                            + " wasn't found data schema for " + child.getNodeType().getLocalName());
+                } else {
+                    throw new UnsupportedDataTypeException("Probably the data node \""
+                            + child.getNodeType().getLocalName() + "\" is not conform to schema");
+                }
             }
 
+            longestPathToElementViaChoiceCase = resolveLongerPath(longestPathToElementViaChoiceCase,
+                    schemaLocation.getLocation());
+
+            DataSchemaNode childSchema = schemaLocation.getSchema();
+
             if (childSchema instanceof ContainerSchemaNode) {
                 Preconditions.checkState(child instanceof CompositeNode,
                         "Data representation of Container should be CompositeNode - " + child.getNodeType());
@@ -83,7 +97,10 @@ class JsonMapper {
         }
 
         for (Node<?> child : parent.getChildren()) {
-            DataSchemaNode childSchema = findFirstSchemaForNode(child, parentSchema.getChildNodes());
+            SchemaLocation schemaLocation = findFirstSchemaForNode(child, parentSchema.getChildNodes(),
+                    new ArrayDeque<>(longestPathToElementViaChoiceCase));
+
+            DataSchemaNode childSchema = schemaLocation.getSchema();
             if (childSchema instanceof LeafListSchemaNode) {
                 foundLeafLists.remove((LeafListSchemaNode) childSchema);
             } else if (childSchema instanceof ListSchemaNode) {
@@ -92,10 +109,45 @@ class JsonMapper {
         }
     }
 
-    private DataSchemaNode findFirstSchemaForNode(Node<?> node, Set<DataSchemaNode> dataSchemaNode) {
+    private List<String> resolveLongerPath(List<String> l1, List<String> l2) {
+        return l1.size() > l2.size() ? l1 : l2;
+    }
+
+    private SchemaLocation findFirstSchemaForNode(Node<?> node, Set<DataSchemaNode> dataSchemaNode,
+            Deque<String> pathIterator) {
+        Map<String, ChoiceNode> choiceSubnodes = new HashMap<>();
         for (DataSchemaNode dsn : dataSchemaNode) {
-            if (node.getNodeType().getLocalName().equals(dsn.getQName().getLocalName())) {
-                return dsn;
+            if (dsn instanceof ChoiceNode) {
+                choiceSubnodes.put(dsn.getQName().getLocalName(), (ChoiceNode) dsn);
+            } else if (node.getNodeType().getLocalName().equals(dsn.getQName().getLocalName())) {
+                return new SchemaLocation(dsn);
+            }
+        }
+
+        for (ChoiceNode choiceSubnode : choiceSubnodes.values()) {
+            if ((!pathIterator.isEmpty() && pathIterator.peekLast().equals(choiceSubnode.getQName().getLocalName()))
+                    || pathIterator.isEmpty()) {
+                String pathPartChoice = pathIterator.pollLast();
+                for (ChoiceCaseNode concreteCase : choiceSubnode.getCases()) {
+                    if ((!pathIterator.isEmpty() && pathIterator.peekLast().equals(
+                            concreteCase.getQName().getLocalName()))
+                            || pathIterator.isEmpty()) {
+                        String pathPartCase = pathIterator.pollLast();
+                        SchemaLocation schemaLocation = findFirstSchemaForNode(node, concreteCase.getChildNodes(),
+                                pathIterator);
+                        if (schemaLocation != null) {
+                            schemaLocation.addPathPart(concreteCase.getQName().getLocalName());
+                            schemaLocation.addPathPart(choiceSubnode.getQName().getLocalName());
+                            return schemaLocation;
+                        }
+                        if (pathPartCase != null) {
+                            pathIterator.addLast(pathPartCase);
+                        }
+                    }
+                }
+                if (pathPartChoice != null) {
+                    pathIterator.addLast(pathPartChoice);
+                }
             }
         }
         return null;
diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/SchemaLocation.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/SchemaLocation.java
new file mode 100644 (file)
index 0000000..24055ce
--- /dev/null
@@ -0,0 +1,28 @@
+package org.opendaylight.controller.sal.rest.impl;
+
+import java.util.*;
+
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+
+class SchemaLocation {
+    final private List<String> location = new ArrayList<>();
+    final private DataSchemaNode schema;
+
+    public SchemaLocation(DataSchemaNode schema) {
+        this.schema = schema;
+    }
+
+    DataSchemaNode getSchema() {
+        return schema;
+    }
+
+    List<String> getLocation() {
+        return location;
+    }
+
+    SchemaLocation addPathPart(String partOfPath) {
+        location.add(partOfPath);
+        return this;
+    }
+
+}
index bc941b997e35dd63c8a3250cb52e8ccf66e27314..89d24ad0579b9b78a77a68f121d8f13ca75ef399 100644 (file)
@@ -137,16 +137,22 @@ final class TestUtils {
 
     static String convertCompositeNodeDataAndYangToJson(CompositeNode compositeNode, String yangPath,
             String outputPath, String searchedModuleName, String searchedDataSchemaName) {
-        String jsonResult = null;
-        Set<Module> modules = null;
+        Set<Module> modules = resolveModules(yangPath);
+        Module module = resolveModule(searchedModuleName, modules);
+        DataSchemaNode dataSchemaNode = resolveDataSchemaNode(module, searchedDataSchemaName);
 
         try {
-            modules = TestUtils.loadModules(ToJsonBasicDataTypesTest.class.getResource(yangPath).getPath());
-        } catch (FileNotFoundException e) {
+            return writeCompNodeWithSchemaContextToJson(compositeNode, outputPath, modules, dataSchemaNode);
+        } catch (WebApplicationException | IOException e) {
+            // TODO Auto-generated catch block
             e.printStackTrace();
         }
-        assertNotNull("modules can't be null.", modules);
+        return null;
 
+    }
+
+    static Module resolveModule(String searchedModuleName, Set<Module> modules) {
+        assertNotNull("modules can't be null.", modules);
         Module module = null;
         if (searchedModuleName != null) {
             for (Module m : modules) {
@@ -158,12 +164,24 @@ final class TestUtils {
         } else if (modules.size() == 1) {
             module = modules.iterator().next();
         }
-        assertNotNull("Module is missing", module);
+        return module;
+    }
 
-        assertNotNull("Composite node can't be null", compositeNode);
+    static Set<Module> resolveModules(String yangPath) {
+        Set<Module> modules = null;
+
+        try {
+            modules = TestUtils.loadModules(ToJsonBasicDataTypesTest.class.getResource(yangPath).getPath());
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        }
+
+        return modules;
+    }
+
+    static DataSchemaNode resolveDataSchemaNode(Module module, String searchedDataSchemaName) {
+        assertNotNull("Module is missing", module);
 
-        StructuredDataToJsonProvider structuredDataToJsonProvider = StructuredDataToJsonProvider.INSTANCE;
-        ByteArrayOutputStream byteArrayOS = new ByteArrayOutputStream();
         DataSchemaNode dataSchemaNode = null;
         if (searchedDataSchemaName != null) {
             for (DataSchemaNode dsn : module.getChildNodes()) {
@@ -174,19 +192,23 @@ final class TestUtils {
         } else if (module.getChildNodes().size() == 1) {
             dataSchemaNode = module.getChildNodes().iterator().next();
         }
+        return dataSchemaNode;
+    }
+
+    static String writeCompNodeWithSchemaContextToJson(CompositeNode compositeNode, String outputPath,
+            Set<Module> modules, DataSchemaNode dataSchemaNode) throws IOException, WebApplicationException {
+        String jsonResult;
+
         assertNotNull(dataSchemaNode);
-        // SchemaContextUtil.
+        assertNotNull("Composite node can't be null", compositeNode);
+        ByteArrayOutputStream byteArrayOS = new ByteArrayOutputStream();
 
-        ControllerContext controllerContext = ControllerContext.getInstance();
-        controllerContext.setSchemas(loadSchemaContext(modules));
-        StructuredData structuredData = new StructuredData(compositeNode, dataSchemaNode);
-        try {
-            structuredDataToJsonProvider.writeTo(structuredData, null, null, null, null, null, byteArrayOS);
-        } catch (WebApplicationException | IOException e) {
-            e.printStackTrace();
-        }
-        assertFalse("Returning JSON string can't be empty for node " + dataSchemaNode.getQName().getLocalName(),
-                byteArrayOS.toString().isEmpty());
+        ControllerContext contContext = ControllerContext.getInstance();
+        contContext.setSchemas(loadSchemaContext(modules));
+        
+        StructuredDataToJsonProvider structuredDataToJsonProvider = StructuredDataToJsonProvider.INSTANCE;
+        structuredDataToJsonProvider.writeTo(new StructuredData(compositeNode, dataSchemaNode), null, null, null, null,
+                null, byteArrayOS);
 
         jsonResult = byteArrayOS.toString();
         try {
diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/ToJsonChoiceCaseTest.java b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/ToJsonChoiceCaseTest.java
new file mode 100644 (file)
index 0000000..141ffdb
--- /dev/null
@@ -0,0 +1,131 @@
+package org.opendaylight.controller.sal.restconf.impl.test;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.util.Set;
+
+import javax.activation.UnsupportedDataTypeException;
+import javax.ws.rs.WebApplicationException;
+
+import org.junit.*;
+import org.opendaylight.yangtools.yang.model.api.*;
+
+public class ToJsonChoiceCaseTest {
+
+    private static Set<Module> modules;
+    private static DataSchemaNode dataSchemaNode;
+
+    @BeforeClass
+    public static void initialization() {
+        modules = TestUtils.resolveModules("/yang-to-json-conversion/choice");
+        Module module = TestUtils.resolveModule(null, modules);
+        dataSchemaNode = TestUtils.resolveDataSchemaNode(module, null);
+
+    }
+
+    /**
+     * Test when some data are in one case node and other in another. Exception
+     * expected!!
+     */
+    @Test
+    public void compNodeDataOnVariousChoiceCasePathTest() {
+        boolean exceptionCatched = false;
+        try {
+            TestUtils.writeCompNodeWithSchemaContextToJson(
+                    TestUtils.loadCompositeNode("/yang-to-json-conversion/choice/xml/data_various_path.xml"),
+                    "/yang-to-json-conversion/choice/xml", modules, dataSchemaNode);
+        } catch (UnsupportedDataTypeException e) {
+            exceptionCatched = true;
+
+        } catch (WebApplicationException | IOException e) {
+            // shouldn't end here
+            assertTrue(false);
+        }
+
+        assertTrue(exceptionCatched);
+
+    }
+
+    /**
+     * Test when second level data are red first, then first and at the end
+     * third level. Level represents pass through couple choice-case
+     */
+    @Ignore
+    @Test
+    public void compNodeDataWithRandomOrderAccordingLevel() {
+        try {
+            String jsonOutput = TestUtils.writeCompNodeWithSchemaContextToJson(
+                    TestUtils.loadCompositeNode("/yang-to-json-conversion/choice/xml/data_random_level.xml"),
+                    "/yang-to-json-conversion/choice/xml", modules, dataSchemaNode);
+        } catch (WebApplicationException | IOException e) {
+            // shouldn't end here
+            assertTrue(false);
+        }
+    }
+
+    /**
+     * Test when element from no first case is used
+     */
+    @Ignore
+    @Test
+    public void compNodeDataNoFirstCase() {
+        try {
+            String jsonOutput = TestUtils.writeCompNodeWithSchemaContextToJson(
+                    TestUtils.loadCompositeNode("/yang-to-json-conversion/choice/xml/data_no_first_case.xml"),
+                    "/yang-to-json-conversion/choice/xml", modules, dataSchemaNode);
+        } catch (WebApplicationException | IOException e) {
+            // shouldn't end here
+            assertTrue(false);
+        }
+    }
+
+    /**
+     * Test when element in case is list
+     */
+    @Ignore
+    @Test
+    public void compNodeDataAsList() {
+        try {
+            String jsonOutput = TestUtils.writeCompNodeWithSchemaContextToJson(
+                    TestUtils.loadCompositeNode("/yang-to-json-conversion/choice/xml/data_list.xml"),
+                    "/yang-to-json-conversion/choice/xml", modules, dataSchemaNode);
+        } catch (WebApplicationException | IOException e) {
+            // shouldn't end here
+            assertTrue(false);
+        }
+    }
+
+    /**
+     * Test when element in case is container
+     */
+    @Ignore
+    @Test
+    public void compNodeDataAsContainer() {
+        try {
+            String jsonOutput = TestUtils.writeCompNodeWithSchemaContextToJson(
+                    TestUtils.loadCompositeNode("/yang-to-json-conversion/choice/xml/data_container.xml"),
+                    "/yang-to-json-conversion/choice/xml", modules, dataSchemaNode);
+        } catch (WebApplicationException | IOException e) {
+            // shouldn't end here
+            assertTrue(false);
+        }
+    }
+
+    /**
+     * Test when element in case is container
+     */
+    @Ignore
+    @Test
+    public void compNodeDataAsLeafList() {
+        try {
+            String jsonOutput = TestUtils.writeCompNodeWithSchemaContextToJson(
+                    TestUtils.loadCompositeNode("/yang-to-json-conversion/choice/xml/data_leaflist.xml"),
+                    "/yang-to-json-conversion/choice/xml", modules, dataSchemaNode);
+        } catch (WebApplicationException | IOException e) {
+            // shouldn't end here
+            assertTrue(false);
+        }
+    }
+
+}
diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/choice.yang b/opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/choice.yang
new file mode 100644 (file)
index 0000000..8b02a92
--- /dev/null
@@ -0,0 +1,97 @@
+module choice-case-test {
+       namespace "choice:case:test";  
+       
+       prefix "chcatst";
+       revision 2013-11-27 {    
+       }
+       
+       container cont {
+               leaf lf1 {
+                       type string;
+               }               
+               
+               choice choi1 {
+                       case a1 {
+                               leaf lf1a {
+                                       type uint16;    
+                               }
+                               choice choi1a {
+                                       case aa1 {
+                                               leaf lf1aa {
+                                                       type string;
+                                               }
+                                               choice choi1aa {
+                                                       case aaa1 {
+                                                               leaf lf1aaa {
+                                                                       type string;
+                                                               }
+                                                       }
+                                                       case aab1 {
+                                                               leaf lf1aab {
+                                                                       type string;
+                                                               }
+                                                       }
+                                               }
+                                       }
+                                       case ab1 {
+                                               leaf lf1ab {
+                                                       type string;
+                                               }
+                                       }
+                               }
+                       }                       
+                       case b1 {
+                               list lst1b {
+                                       leaf lf11b {
+                                               type string;
+                                       }
+                               }
+                       }
+                       case c1 {
+                               container cont1c {
+                                       leaf lf11c {
+                                               type string;
+                                       }
+                               }
+                       }
+                       case d1 {
+                               leaf-list lflst1d {
+                                       type string;
+                               }
+                       }
+               }
+               
+               choice choi2 {
+                       case a2 {
+                               leaf lf2a {
+                                       type string;
+                               }
+                       }
+                       case b2 {
+                               leaf lf2b {
+                                       type string;
+                               }
+                       }
+               }
+               
+/*              equal identifiers in various cases are illegal 7.9.2 rfc6020 */
+/*             
+               choice choi3 {
+                       case 3a {
+                               leaf lf3a {
+                                       type string;
+                               }
+                       }
+                       case 3b {
+                               leaf lf3b {
+                                       type string;
+                               }
+                       }
+               }
+*/
+                               
+       }
+  
+       
+
+}
\ No newline at end of file
diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/xml/data_container.xml b/opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/xml/data_container.xml
new file mode 100644 (file)
index 0000000..9c75194
--- /dev/null
@@ -0,0 +1,5 @@
+<cont>
+       <cont1c>
+               <lf11c>lf11c val</lf11c>
+       </cont1c>
+</cont>
\ No newline at end of file
diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/xml/data_leaflist.xml b/opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/xml/data_leaflist.xml
new file mode 100644 (file)
index 0000000..6cebb64
--- /dev/null
@@ -0,0 +1,4 @@
+<cont>
+       <lflst1d>lflst1d_1 val</lflst1d>
+       <lflst1d>lflst1d_2 val</lflst1d>
+</cont>
\ No newline at end of file
diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/xml/data_list.xml b/opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/xml/data_list.xml
new file mode 100644 (file)
index 0000000..710da55
--- /dev/null
@@ -0,0 +1,8 @@
+<cont>
+       <lst1b>
+               <lf11b>lf11b_1 val</lf11b>
+       </lst1b>
+       <lst1b>
+               <lf11b>lf11b_2 val</lf11b>
+       </lst1b>
+</cont>
\ No newline at end of file
diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/xml/data_more_choices_same_level.xml b/opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/xml/data_more_choices_same_level.xml
new file mode 100644 (file)
index 0000000..9c75194
--- /dev/null
@@ -0,0 +1,5 @@
+<cont>
+       <cont1c>
+               <lf11c>lf11c val</lf11c>
+       </cont1c>
+</cont>
\ No newline at end of file
diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/xml/data_no_first_case.xml b/opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/xml/data_no_first_case.xml
new file mode 100644 (file)
index 0000000..43e9974
--- /dev/null
@@ -0,0 +1,5 @@
+<cont>
+       <lf1>lf1 val</lf1>
+       <lf1a>121</lf1a>
+       <lf1ab>lf1ab val</lf1ab>
+</cont>
\ No newline at end of file
diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/xml/data_random_level.xml b/opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/xml/data_random_level.xml
new file mode 100644 (file)
index 0000000..b1b78e4
--- /dev/null
@@ -0,0 +1,6 @@
+<cont>
+       <lf1aa>lf1aa val</lf1aa>
+       <lf1>lf1 val</lf1>
+       <lf1a>121</lf1a>
+       <lf1aaa>lf1aaa val</lf1aaa>
+</cont>
\ No newline at end of file
diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/xml/data_various_path.xml b/opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/xml/data_various_path.xml
new file mode 100644 (file)
index 0000000..c43dab6
--- /dev/null
@@ -0,0 +1,6 @@
+<cont>
+       <lf1aa>lf1aa val</lf1aa>
+       <lf1>lf1 val</lf1>
+       <lf1a>121</lf1a>
+       <lf1ab>lf1ab value</lf1ab>      
+</cont>
\ No newline at end of file