From 8cddf0e3f0a64fcc87638effe18282f83bc421c0 Mon Sep 17 00:00:00 2001 From: Jozef Gloncak Date: Mon, 3 Nov 2014 16:02:54 +0100 Subject: [PATCH] BUG 2282 - JSON top level element without module name 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 (cherry picked from commit 6eee2a1ce9e69a7b653a7b4427573eff67588d1f) --- .../data/codec/gson/JsonParserStream.java | 104 ++++++++---- .../yang/data/codec/gson/RpcAsContainer.java | 150 ++++++++++++++++++ .../gson/JsonStreamToNormalizedNodeTest.java | 52 ++++++ ...estingNormalizedNodeStructuresCreator.java | 3 + .../missing-module-in-top-level.json | 4 + .../test/resources/complexjson/namesakes.json | 5 + .../complexjson/not-existing-element.json | 5 + .../unkeyed-node-in-container.json | 2 +- .../complexjson-augmentation-namesake.yang | 18 +++ .../yang/complexjson-augmentation.yang | 30 ++-- 10 files changed, 327 insertions(+), 46 deletions(-) create mode 100644 yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/RpcAsContainer.java create mode 100644 yang/yang-data-codec-gson/src/test/resources/complexjson/missing-module-in-top-level.json create mode 100644 yang/yang-data-codec-gson/src/test/resources/complexjson/namesakes.json create mode 100644 yang/yang-data-codec-gson/src/test/resources/complexjson/not-existing-element.json create mode 100644 yang/yang-data-codec-gson/src/test/resources/complexjson/yang/complexjson-augmentation-namesake.yang diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JsonParserStream.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JsonParserStream.java index 1721f428d5..4f832a8dbd 100644 --- a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JsonParserStream.java +++ b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JsonParserStream.java @@ -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 namesakes = new HashSet<>(); + final Set 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 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 namespaceOpt = namespace == null ? Optional. absent() : Optional.of(namespace); - return new NamespaceAndName(nodeNamePart, namespaceOpt); + if (namespace == null) { + Set 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 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 resolveAllPotentialNamespaces(final String elementName, final DataSchemaNode dataSchemaNode) { + final Set potentialUris = new HashSet<>(); + final Set 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 findSchemaNodeByNameAndNamespace(final DataSchemaNode dataSchemaNode, final String childName, final URI namespace) { final Deque result = new ArrayDeque<>(); - List childChoices = new ArrayList<>(); + final List 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 resultFromRecursion = findSchemaNodeByNameAndNamespace(concreteCase, childName, + for (final ChoiceNode choiceNode : childChoices) { + for (final ChoiceCaseNode concreteCase : choiceNode.getCases()) { + final Deque 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; + private final URI uri; private final String name; - public NamespaceAndName(final String name, final Optional 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 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 index 0000000000..0b64b22967 --- /dev/null +++ b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/RpcAsContainer.java @@ -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> getTypeDefinitions() { + return delegate.getTypeDefinitions(); + } + + @Override + public Set 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 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 getUses() { + return Collections.emptySet(); + } + + @Override + public Set getAvailableAugmentations() { + return Collections.emptySet(); + } + + @Override + public boolean isPresenceContainer() { + return false; + } + + @Override + public Collection getChildNodes() { + final ArrayList 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; + } + +} diff --git a/yang/yang-data-codec-gson/src/test/java/org/opendaylight/yangtools/yang/data/codec/gson/JsonStreamToNormalizedNodeTest.java b/yang/yang-data-codec-gson/src/test/java/org/opendaylight/yangtools/yang/data/codec/gson/JsonStreamToNormalizedNodeTest.java index 56d12104bd..1a71142be5 100644 --- a/yang/yang-data-codec-gson/src/test/java/org/opendaylight/yangtools/yang/data/codec/gson/JsonStreamToNormalizedNodeTest.java +++ b/yang/yang-data-codec-gson/src/test/java/org/opendaylight/yangtools/yang/data/codec/gson/JsonStreamToNormalizedNodeTest.java @@ -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(); diff --git a/yang/yang-data-codec-gson/src/test/java/org/opendaylight/yangtools/yang/data/codec/gson/TestingNormalizedNodeStructuresCreator.java b/yang/yang-data-codec-gson/src/test/java/org/opendaylight/yangtools/yang/data/codec/gson/TestingNormalizedNodeStructuresCreator.java index 140c7ff008..941c55a104 100644 --- a/yang/yang-data-codec-gson/src/test/java/org/opendaylight/yangtools/yang/data/codec/gson/TestingNormalizedNodeStructuresCreator.java +++ b/yang/yang-data-codec-gson/src/test/java/org/opendaylight/yangtools/yang/data/codec/gson/TestingNormalizedNodeStructuresCreator.java @@ -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 index 0000000000..1070c36db3 --- /dev/null +++ b/yang/yang-data-codec-gson/src/test/resources/complexjson/missing-module-in-top-level.json @@ -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 index 0000000000..9c0fab40e7 --- /dev/null +++ b/yang/yang-data-codec-gson/src/test/resources/complexjson/namesakes.json @@ -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 index 0000000000..ebc44bbb18 --- /dev/null +++ b/yang/yang-data-codec-gson/src/test/resources/complexjson/not-existing-element.json @@ -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/unkeyed-node-in-container.json b/yang/yang-data-codec-gson/src/test/resources/complexjson/unkeyed-node-in-container.json index 8b3e1e3897..61bd71d1ab 100644 --- a/yang/yang-data-codec-gson/src/test/resources/complexjson/unkeyed-node-in-container.json +++ b/yang/yang-data-codec-gson/src/test/resources/complexjson/unkeyed-node-in-container.json @@ -6,4 +6,4 @@ } ] } -} +} \ 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 index 0000000000..e94acb2374 --- /dev/null +++ b/yang/yang-data-codec-gson/src/test/resources/complexjson/yang/complexjson-augmentation-namesake.yang @@ -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; + } + } + +} diff --git a/yang/yang-data-codec-gson/src/test/resources/complexjson/yang/complexjson-augmentation.yang b/yang/yang-data-codec-gson/src/test/resources/complexjson/yang/complexjson-augmentation.yang index d4ea623b19..a4cf5adeb5 100644 --- a/yang/yang-data-codec-gson/src/test/resources/complexjson/yang/complexjson-augmentation.yang +++ b/yang/yang-data-codec-gson/src/test/resources/complexjson/yang/complexjson-augmentation.yang @@ -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; } - } + } } -- 2.36.6