X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=yang%2Fyang-data-codec-gson%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fyangtools%2Fyang%2Fdata%2Fcodec%2Fgson%2FJsonParserStream.java;h=f184ce7b5e87d2595766d30c97ac94f4307109d4;hb=ded9e3c33f7138c7391caf5afb377dce76986f8f;hp=65c234aae4b87dfef3c87fd3662cf34fd2c660d2;hpb=3df22b4570660649c40b12e780ec7102b62c4918;p=yangtools.git 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 65c234aae4..f184ce7b5e 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; @@ -23,24 +21,25 @@ import java.io.IOException; 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.DataSchemaNodeAwareAdaptor; import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.data.api.schema.stream.SchemaAwareNormalizedNodeStreamWriter; import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode; import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode; -import org.opendaylight.yangtools.yang.model.api.ChoiceNode; +import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode; import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; 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.TypeDefinition; +import org.opendaylight.yangtools.yang.model.api.SchemaNode; +import org.opendaylight.yangtools.yang.model.api.YangModeledAnyXmlSchemaNode; /** * This class parses JSON elements from a GSON JsonReader. It disallows multiple elements of the same name unlike the @@ -49,48 +48,56 @@ import org.opendaylight.yangtools.yang.model.api.TypeDefinition; @Beta public final class JsonParserStream implements Closeable, Flushable { private final Deque namespaces = new ArrayDeque<>(); - private final NormalizedNodeStreamWriter writer; - private final CodecFactory codecs; + private final SchemaAwareNormalizedNodeStreamWriter writer; + private final JSONCodecFactory codecs; private final SchemaContext schema; + private final DataSchemaNode parentNode; - private JsonParserStream(final NormalizedNodeStreamWriter writer, final SchemaContext schemaContext) { + private JsonParserStream(final NormalizedNodeStreamWriter writer, final SchemaContext schemaContext, + final DataSchemaNode parentNode) { this.schema = Preconditions.checkNotNull(schemaContext); - this.writer = Preconditions.checkNotNull(writer); - this.codecs = CodecFactory.create(schemaContext); + this.writer = DataSchemaNodeAwareAdaptor.forWriter(writer); + this.codecs = JSONCodecFactory.create(schemaContext); + this.parentNode = parentNode; + } + + 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); } public static JsonParserStream create(final NormalizedNodeStreamWriter writer, final SchemaContext schemaContext) { - return new JsonParserStream(writer, schemaContext); + return new JsonParserStream(writer, schemaContext, schemaContext); } - public JsonParserStream parse(final JsonReader reader) throws JsonIOException, JsonSyntaxException { + public JsonParserStream parse(final JsonReader reader) { // 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(schema); + 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 | NumberFormatException e) { throw new JsonSyntaxException(e); - } catch (IOException e) { + } catch (final IOException e) { throw new JsonIOException(e); - } catch (NumberFormatException e) { - throw new JsonSyntaxException(e); } catch (StackOverflowError | OutOfMemoryError e) { throw new JsonParseException("Failed parsing JSON source: " + reader + " to Json", e); } finally { @@ -98,14 +105,18 @@ public final class JsonParserStream implements Closeable, Flushable { } } - private final void setValue(final AbstractNodeDataWithSchema parent, final String value) { - Preconditions.checkArgument(parent instanceof SimpleNodeDataWithSchema, "Node %s is not a simple type", parent); + private void setValue(final AbstractNodeDataWithSchema parent, final String value) { + Preconditions.checkArgument(parent instanceof SimpleNodeDataWithSchema, "Node %s is not a simple type", + parent.getSchema().getQName()); + final SimpleNodeDataWithSchema parentSimpleNode = (SimpleNodeDataWithSchema) parent; + Preconditions.checkArgument(parentSimpleNode.getValue() == null, "Node '%s' has already set its value to '%s'", + parentSimpleNode.getSchema().getQName(), parentSimpleNode.getValue()); - final Object translatedValue = translateValueByType(value, parent.getSchema()); - ((SimpleNodeDataWithSchema) parent).setValue(translatedValue); + final Object translatedValue = translateValueByType(value, parentSimpleNode.getSchema()); + parentSimpleNode.setValue(translatedValue); } - public void read(final JsonReader in, final AbstractNodeDataWithSchema parent) throws IOException { + public void read(final JsonReader in, AbstractNodeDataWithSchema parent) throws IOException { switch (in.peek()) { case STRING: case NUMBER: @@ -121,40 +132,55 @@ public final class JsonParserStream implements Closeable, Flushable { case BEGIN_ARRAY: in.beginArray(); while (in.hasNext()) { - AbstractNodeDataWithSchema newChild = null; - if (parent instanceof ListNodeDataWithSchema) { - newChild = new ListEntryNodeDataWithSchema(parent.getSchema()); - ((CompositeNodeDataWithSchema) parent).addChild(newChild); - } else if (parent instanceof LeafListNodeDataWithSchema) { - newChild = new LeafListEntryNodeDataWithSchema(parent.getSchema()); - ((CompositeNodeDataWithSchema) parent).addChild(newChild); + if (parent instanceof LeafNodeDataWithSchema) { + read(in, parent); + } else { + final AbstractNodeDataWithSchema newChild = newArrayEntry(parent); + read(in, newChild); } - read(in, newChild); } in.endArray(); return; case BEGIN_OBJECT: - Set namesakes = new HashSet<>(); + final Set namesakes = new HashSet<>(); in.beginObject(); + /* + * This allows parsing of incorrectly /as showcased/ + * in testconf nesting of list items - eg. + * lists with one value are sometimes serialized + * without wrapping array. + * + */ + if(isArray(parent)) { + parent = newArrayEntry(parent); + } while (in.hasNext()) { final String jsonElementName = in.nextName(); - final NamespaceAndName namespaceAndName = resolveNamespace(jsonElementName); + DataSchemaNode parentSchema = parent.getSchema(); + if (parentSchema instanceof YangModeledAnyXmlSchemaNode) { + parentSchema = ((YangModeledAnyXmlSchemaNode) parentSchema).getSchemaOfAnyXmlData(); + } + final NamespaceAndName namespaceAndName = resolveNamespace(jsonElementName, parentSchema); final String localName = namespaceAndName.getName(); addNamespace(namespaceAndName.getUri()); if (namesakes.contains(jsonElementName)) { throw new JsonSyntaxException("Duplicate name " + jsonElementName + " in JSON input."); } namesakes.add(jsonElementName); - final Deque childDataSchemaNodes = findSchemaNodeByNameAndNamespace(parent.getSchema(), + + final Deque childDataSchemaNodes = findSchemaNodeByNameAndNamespace(parentSchema, localName, getCurrentNamespace()); if (childDataSchemaNodes.isEmpty()) { throw new IllegalStateException("Schema for node with name " + localName + " and namespace " + getCurrentNamespace() + " doesn't exist."); } - AbstractNodeDataWithSchema newChild; - newChild = ((CompositeNodeDataWithSchema) parent).addChild(childDataSchemaNodes); -// FIXME:anyxml data shouldn't be skipped but should be loaded somehow. will be specified after 17AUG2014 + final AbstractNodeDataWithSchema newChild = ((CompositeNodeDataWithSchema) parent).addChild(childDataSchemaNodes); + /* + * FIXME:anyxml data shouldn't be skipped but should be loaded somehow. + * will be able to load anyxml which conforms to YANG data using these + * parser, for other anyxml will be harder. + */ if (newChild instanceof AnyXmlNodeDataWithSchema) { in.skipValue(); } else { @@ -172,53 +198,45 @@ public final class JsonParserStream implements Closeable, Flushable { } } - private Object translateValueByType(final String value, final DataSchemaNode node) { - final TypeDefinition typeDefinition = typeDefinition(node); - if (typeDefinition == null) { - return value; - } - - return codecs.codecFor(typeDefinition).deserialize(value); + private static boolean isArray(final AbstractNodeDataWithSchema parent) { + return parent instanceof ListNodeDataWithSchema || parent instanceof LeafListNodeDataWithSchema; } - private static TypeDefinition typeDefinition(final DataSchemaNode node) { - TypeDefinition baseType = null; - if (node instanceof LeafListSchemaNode) { - baseType = ((LeafListSchemaNode) node).getType(); - } else if (node instanceof LeafSchemaNode) { - baseType = ((LeafSchemaNode) node).getType(); - } else if (node instanceof AnyXmlSchemaNode) { - return null; + private static AbstractNodeDataWithSchema newArrayEntry(final AbstractNodeDataWithSchema parent) { + AbstractNodeDataWithSchema newChild; + if (parent instanceof ListNodeDataWithSchema) { + newChild = new ListEntryNodeDataWithSchema(parent.getSchema()); + } else if (parent instanceof LeafListNodeDataWithSchema) { + newChild = new LeafListEntryNodeDataWithSchema(parent.getSchema()); } else { - throw new IllegalArgumentException("Unhandled parameter types: " + Arrays. asList(node).toString()); + throw new IllegalStateException("Found an unexpected array nested under "+ parent.getSchema().getQName()); } + ((CompositeNodeDataWithSchema) parent).addChild(newChild); + return newChild; + } - if (baseType != null) { - while (baseType.getBaseType() != null) { - baseType = baseType.getBaseType(); - } + private Object translateValueByType(final String value, final DataSchemaNode node) { + if (node instanceof AnyXmlSchemaNode) { + /* + * FIXME: Figure out some YANG extension dispatch, which will + * reuse JSON parsing or XML parsing - anyxml is not well-defined in + * JSON. + */ + return value; } - return baseType; + return codecs.codecFor(node).deserialize(value); } private void removeNamespace() { 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; @@ -232,8 +250,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 under "+dataSchemaNode.getQName()+"."); + } + } + + 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 ChoiceSchemaNode) { + choices.add((ChoiceSchemaNode)childSchemaNode); + } else if (childSchemaNode.getQName().getLocalName().equals(elementName)) { + potentialUris.add(childSchemaNode.getQName().getNamespace()); + } + } + + for (final ChoiceSchemaNode choiceNode : choices) { + for (final ChoiceCaseNode concreteCase : choiceNode.getCases()) { + potentialUris.addAll(resolveAllPotentialNamespaces(elementName, concreteCase)); + } + } + } + return potentialUris; } private URI getCurrentNamespace() { @@ -254,24 +316,33 @@ 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<>(); + DataSchemaNode potentialChildNode = null; if (dataSchemaNode instanceof DataNodeContainer) { - for (DataSchemaNode childNode : ((DataNodeContainer) dataSchemaNode).getChildNodes()) { - if (childNode instanceof ChoiceNode) { - childChoices.add((ChoiceNode) childNode); + for (final DataSchemaNode childNode : ((DataNodeContainer) dataSchemaNode).getChildNodes()) { + if (childNode instanceof ChoiceSchemaNode) { + childChoices.add((ChoiceSchemaNode) childNode); } else { final QName childQName = childNode.getQName(); + if (childQName.getLocalName().equals(childName) && childQName.getNamespace().equals(namespace)) { - result.push(childNode); - return result; + if (potentialChildNode == null || + childQName.getRevision().after(potentialChildNode.getQName().getRevision())) { + potentialChildNode = childNode; + } } } } } + if (potentialChildNode != null) { + result.push(potentialChildNode); + return result; + } + // 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 ChoiceSchemaNode choiceNode : childChoices) { + for (final ChoiceCaseNode concreteCase : choiceNode.getCases()) { + final Deque resultFromRecursion = findSchemaNodeByNameAndNamespace(concreteCase, childName, namespace); if (!resultFromRecursion.isEmpty()) { resultFromRecursion.push(concreteCase); @@ -284,10 +355,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; } @@ -296,7 +367,7 @@ public final class JsonParserStream implements Closeable, Flushable { return name; } - public Optional getUri() { + public URI getUri() { return uri; } }