From: Ed Warnicke Date: Fri, 29 Nov 2013 11:16:05 +0000 (+0000) Subject: Merge "Choice and case resolving in JSON output" X-Git-Tag: jenkins-controller-bulk-release-prepare-only-2-1~305 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=commitdiff_plain;h=49bfc449558d0306f0b6550bc5bdf41e5cafca44;hp=1014b267627455327f831f087af2cf1ce4d8f3f3 Merge "Choice and case resolving in JSON output" --- diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonMapper.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonMapper.java index 351ae6ebbe..36b46a171c 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonMapper.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonMapper.java @@ -47,13 +47,27 @@ class JsonMapper { checkNotNull(parent); checkNotNull(parentSchema); + List 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 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) { + private List resolveLongerPath(List l1, List l2) { + return l1.size() > l2.size() ? l1 : l2; + } + + private SchemaLocation findFirstSchemaForNode(Node node, Set dataSchemaNode, + Deque pathIterator) { + Map 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 index 0000000000..24055ced67 --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/SchemaLocation.java @@ -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 location = new ArrayList<>(); + final private DataSchemaNode schema; + + public SchemaLocation(DataSchemaNode schema) { + this.schema = schema; + } + + DataSchemaNode getSchema() { + return schema; + } + + List getLocation() { + return location; + } + + SchemaLocation addPathPart(String partOfPath) { + location.add(partOfPath); + return this; + } + +} diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/TestUtils.java b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/TestUtils.java index bc941b997e..89d24ad057 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/TestUtils.java +++ b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/TestUtils.java @@ -137,16 +137,22 @@ final class TestUtils { static String convertCompositeNodeDataAndYangToJson(CompositeNode compositeNode, String yangPath, String outputPath, String searchedModuleName, String searchedDataSchemaName) { - String jsonResult = null; - Set modules = null; + Set 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 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 resolveModules(String yangPath) { + Set 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 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 index 0000000000..141ffdb754 --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/ToJsonChoiceCaseTest.java @@ -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 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 index 0000000000..8b02a92979 --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/choice.yang @@ -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 index 0000000000..9c751949d0 --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/xml/data_container.xml @@ -0,0 +1,5 @@ + + + lf11c val + + \ 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 index 0000000000..6cebb6424a --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/xml/data_leaflist.xml @@ -0,0 +1,4 @@ + + lflst1d_1 val + lflst1d_2 val + \ 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 index 0000000000..710da55de6 --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/xml/data_list.xml @@ -0,0 +1,8 @@ + + + lf11b_1 val + + + lf11b_2 val + + \ 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 index 0000000000..9c751949d0 --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/xml/data_more_choices_same_level.xml @@ -0,0 +1,5 @@ + + + lf11c val + + \ 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 index 0000000000..43e9974a2c --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/xml/data_no_first_case.xml @@ -0,0 +1,5 @@ + + lf1 val + 121 + lf1ab val + \ 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 index 0000000000..b1b78e4744 --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/xml/data_random_level.xml @@ -0,0 +1,6 @@ + + lf1aa val + lf1 val + 121 + lf1aaa val + \ 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 index 0000000000..c43dab60c0 --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/test/resources/yang-to-json-conversion/choice/xml/data_various_path.xml @@ -0,0 +1,6 @@ + + lf1aa val + lf1 val + 121 + lf1ab value + \ No newline at end of file