From 1735c52eb9629fb67c2c1499c3291f27bbc632e7 Mon Sep 17 00:00:00 2001 From: Jozef Gloncak Date: Thu, 21 Aug 2014 15:00:10 +0200 Subject: [PATCH] BUG 1440 - json stream to normalized node stream writer JsonParserStream contains implementation of read method in which there is gradually constructed structure of NodeDataWithSchema nodes. NodeDataWithSchema is similar to SchemaNode but contains only reference to children which really exists in JSON input and concrete values for nodes of type leaf or leaf list. It was necessary to firstly load JSON to NodeDataWitchSchema structure because for some types (composite keys of list, all nodes belonging to one augment, all nodes belonging to one choice) it isn't possible to do sequential processing (it means to call concrete opening and closing methods of NormalizedNodeStreamWriter interface). JsonParserStream constructor requires schema context as input parameter. If data behind mount point should be parsed then schema context of mount point is required. Change-Id: I28c1d3193792feb875ec2ceda0035ca56bfa5d42 Signed-off-by: Jozef Gloncak Signed-off-by: Robert Varga --- yang/pom.xml | 1 + .../LoggingNormalizedNodeStreamWriter.java | 119 ++++++ .../schema/stream/NormalizedNodeWriter.java | 131 ++++++ yang/yang-data-codec-gson/pom.xml | 47 +++ .../gson/AbstractNodeDataWithSchema.java | 65 +++ .../codec/gson/AnyXmlNodeDataWithSchema.java | 26 ++ .../codec/gson/CaseNodeDataWithSchema.java | 25 ++ .../codec/gson/ChoiceNodeDataWithSchema.java | 51 +++ .../gson/CompositeNodeDataWithSchema.java | 234 +++++++++++ .../gson/ContainerNodeDataWithSchema.java | 30 ++ .../gson/JSONNormalizedNodeStreamWriter.java | 314 ++++++++++++++ .../data/codec/gson/JsonParserStream.java | 388 ++++++++++++++++++ .../gson/LeafListEntryNodeDataWithSchema.java | 24 ++ .../gson/LeafListNodeDataWithSchema.java | 28 ++ .../codec/gson/LeafNodeDataWithSchema.java | 26 ++ .../gson/ListEntryNodeDataWithSchema.java | 77 ++++ .../codec/gson/ListNodeDataWithSchema.java | 35 ++ .../codec/gson/SimpleNodeDataWithSchema.java | 28 ++ .../codec/gson/helpers/AbstractCodecImpl.java | 49 +++ .../codec/gson/helpers/IdentityValuesDTO.java | 155 +++++++ .../gson/helpers/IdentityrefCodecImpl.java | 42 ++ .../helpers/InstanceIdentifierCodecImpl.java | 151 +++++++ .../codec/gson/helpers/LeafrefCodecImpl.java | 24 ++ .../data/codec/gson/helpers/ObjectCodec.java | 117 ++++++ .../codec/gson/helpers/RestCodecFactory.java | 35 ++ .../data/codec/gson/helpers/RestUtil.java | 160 ++++++++ .../gson/helpers/SchemaContextUtils.java | 141 +++++++ .../gson/StreamToNormalizedNodeTest.java | 153 +++++++ .../resources/complexjson/complex-json.json | 41 ++ .../yang/complexjson-augmentation.yang | 37 ++ .../complexjson/yang/complexjson.yang | 140 +++++++ 31 files changed, 2894 insertions(+) create mode 100644 yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/stream/LoggingNormalizedNodeStreamWriter.java create mode 100644 yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/stream/NormalizedNodeWriter.java create mode 100644 yang/yang-data-codec-gson/pom.xml create mode 100644 yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/AbstractNodeDataWithSchema.java create mode 100644 yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/AnyXmlNodeDataWithSchema.java create mode 100644 yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/CaseNodeDataWithSchema.java create mode 100644 yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/ChoiceNodeDataWithSchema.java create mode 100644 yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/CompositeNodeDataWithSchema.java create mode 100644 yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/ContainerNodeDataWithSchema.java create mode 100644 yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONNormalizedNodeStreamWriter.java create mode 100644 yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JsonParserStream.java create mode 100644 yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/LeafListEntryNodeDataWithSchema.java create mode 100644 yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/LeafListNodeDataWithSchema.java create mode 100644 yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/LeafNodeDataWithSchema.java create mode 100644 yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/ListEntryNodeDataWithSchema.java create mode 100644 yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/ListNodeDataWithSchema.java create mode 100644 yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/SimpleNodeDataWithSchema.java create mode 100644 yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/AbstractCodecImpl.java create mode 100644 yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/IdentityValuesDTO.java create mode 100644 yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/IdentityrefCodecImpl.java create mode 100644 yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/InstanceIdentifierCodecImpl.java create mode 100644 yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/LeafrefCodecImpl.java create mode 100644 yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/ObjectCodec.java create mode 100644 yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/RestCodecFactory.java create mode 100644 yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/RestUtil.java create mode 100644 yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/SchemaContextUtils.java create mode 100644 yang/yang-data-codec-gson/src/test/java/org/opendaylight/yangtools/yang/data/codec/gson/StreamToNormalizedNodeTest.java create mode 100644 yang/yang-data-codec-gson/src/test/resources/complexjson/complex-json.json create mode 100644 yang/yang-data-codec-gson/src/test/resources/complexjson/yang/complexjson-augmentation.yang create mode 100644 yang/yang-data-codec-gson/src/test/resources/complexjson/yang/complexjson.yang diff --git a/yang/pom.xml b/yang/pom.xml index a7e7cc77ad..2e12378abb 100644 --- a/yang/pom.xml +++ b/yang/pom.xml @@ -22,6 +22,7 @@ yang-data-util yang-data-impl yang-data-operations + yang-data-codec-gson yang-model-api yang-maven-plugin yang-maven-plugin-it diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/stream/LoggingNormalizedNodeStreamWriter.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/stream/LoggingNormalizedNodeStreamWriter.java new file mode 100644 index 0000000000..83a8c33180 --- /dev/null +++ b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/stream/LoggingNormalizedNodeStreamWriter.java @@ -0,0 +1,119 @@ +package org.opendaylight.yangtools.yang.data.api.schema.stream; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; + +import java.io.IOException; + +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Beta +public class LoggingNormalizedNodeStreamWriter implements NormalizedNodeStreamWriter { + private static final Logger LOG = LoggerFactory.getLogger(LoggingNormalizedNodeStreamWriter.class); + private static final int DEFAULT_INDENT_SIZE = 2; + private final int indentSize = DEFAULT_INDENT_SIZE; + private int currentIndent = 0; + + private String ind() { + return Strings.repeat(" ", currentIndent); + } + + private void decIndent() { + Preconditions.checkState(currentIndent >= 0, "Unexpected indentation %s", currentIndent); + currentIndent -= indentSize; + } + + private void incIndent() { + currentIndent += indentSize; + } + + @Override + public void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint) throws IllegalStateException { + LOG.debug("{}{}[](no key)", ind(), name); + incIndent(); + } + + @Override + public void startUnkeyedList(final NodeIdentifier name, final int childSizeHint) throws IllegalArgumentException { + LOG.debug("{}{}(no key)", ind(), name); + incIndent(); + } + + @Override + public void startOrderedMapNode(final NodeIdentifier name, final int childSizeHint) throws IllegalArgumentException { + + } + + @Override + public void startMapNode(final NodeIdentifier name, final int childSizeHint) throws IllegalArgumentException { + LOG.debug("{}{}(key)", ind(), name); + incIndent(); + } + + @Override + public void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint) + throws IllegalArgumentException { + LOG.debug("{}{}[](key)", ind(), identifier); + incIndent(); + } + + @Override + public void startLeafSet(final NodeIdentifier name, final int childSizeHint) throws IllegalArgumentException { + LOG.debug("{}{}(leaf-list)", ind(), name); + incIndent(); + } + + @Override + public void startContainerNode(final NodeIdentifier name, final int childSizeHint) throws IllegalArgumentException { + LOG.debug("{}{}(container)", ind(), name); + incIndent(); + } + + @Override + public void startChoiceNode(final NodeIdentifier name, final int childSizeHint) throws IllegalArgumentException { + LOG.debug("{}{}(choice)", ind(), name); + incIndent(); + } + + @Override + public void startAugmentationNode(final AugmentationIdentifier identifier) throws IllegalArgumentException { + LOG.debug("{}{}(augmentation)", ind(), identifier); + incIndent(); + } + + @Override + public void leafSetEntryNode(final Object value) throws IllegalArgumentException { + LOG.debug("{}{}({}) ", ind(), value, value.getClass().getSimpleName()); + } + + @Override + public void leafNode(final NodeIdentifier name, final Object value) throws IllegalArgumentException { + LOG.debug("{}{}(leaf({}))=", ind(), name, value.getClass().getSimpleName(), value); + } + + @Override + public void endNode() throws IllegalStateException { + decIndent(); + LOG.debug("{}(end)", ind()); + } + + @Override + public void anyxmlNode(final NodeIdentifier name, final Object value) throws IllegalArgumentException { + LOG.debug("{}{}(anyxml)=", ind(), name, value); + } + + @Override + public void flush() throws IOException { + LOG.trace("<>"); + } + + @Override + public void close() throws IOException { + LOG.debug("<>"); + } +} \ No newline at end of file diff --git a/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/stream/NormalizedNodeWriter.java b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/stream/NormalizedNodeWriter.java new file mode 100644 index 0000000000..a0068c303a --- /dev/null +++ b/yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/stream/NormalizedNodeWriter.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2014 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.api.schema.stream; + +import static org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter.UNKNOWN_SIZE; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; + +import java.io.Closeable; +import java.io.Flushable; +import java.io.IOException; + +import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode; +import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode; +import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; +import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode; +import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.MapNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.OrderedMapNode; +import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode; + +/** + * This is an experimental + */ +@Beta +public final class NormalizedNodeWriter implements Closeable, Flushable { + private final NormalizedNodeStreamWriter writer; + + private NormalizedNodeWriter(final NormalizedNodeStreamWriter writer) { + this.writer = Preconditions.checkNotNull(writer); + } + + public static NormalizedNodeWriter forStreamWriter(final NormalizedNodeStreamWriter writer) { + return new NormalizedNodeWriter(writer); + } + + public NormalizedNodeWriter write(final NormalizedNode node) throws IOException { + if (wasProcessedAsCompositeNode(node)) { + return this; + } + + if (wasProcessAsSimpleNode(node)) { + return this; + } + + throw new IllegalStateException("It wasn't possible to serialize node " + node); + } + + private boolean wasProcessAsSimpleNode(final NormalizedNode node) throws IOException { + if (node instanceof LeafSetEntryNode) { + final LeafSetEntryNode nodeAsLeafList = (LeafSetEntryNode)node; + writer.leafSetEntryNode(nodeAsLeafList.getValue()); + return true; + } else if (node instanceof LeafNode) { + final LeafNode nodeAsLeaf = (LeafNode)node; + writer.leafNode(nodeAsLeaf.getIdentifier(), nodeAsLeaf.getValue()); + return true; + } else if (node instanceof AnyXmlNode) { + final AnyXmlNode anyXmlNode = (AnyXmlNode)node; + writer.anyxmlNode(anyXmlNode.getIdentifier(), anyXmlNode.getValue()); + return true; + } + + return false; + } + + private boolean wasProcessedAsCompositeNode(final NormalizedNode node) throws IOException { + boolean hasDataContainerChild = false; + if (node instanceof ContainerNode) { + writer.startContainerNode(((ContainerNode) node).getIdentifier(), UNKNOWN_SIZE); + hasDataContainerChild = true; + } else if (node instanceof MapEntryNode) { + writer.startMapEntryNode(((MapEntryNode) node).getIdentifier(), UNKNOWN_SIZE); + hasDataContainerChild = true; + } else if (node instanceof UnkeyedListEntryNode) { + writer.startUnkeyedListItem(((UnkeyedListEntryNode) node).getIdentifier(), UNKNOWN_SIZE); + hasDataContainerChild = true; + } else if (node instanceof ChoiceNode) { + writer.startChoiceNode(((ChoiceNode) node).getIdentifier(), UNKNOWN_SIZE); + hasDataContainerChild = true; + } else if (node instanceof AugmentationNode) { + writer.startAugmentationNode(((AugmentationNode) node).getIdentifier()); + hasDataContainerChild = true; + } else if (node instanceof UnkeyedListNode) { + writer.startUnkeyedList(((UnkeyedListNode) node).getIdentifier(), UNKNOWN_SIZE); + hasDataContainerChild = true; + } else if (node instanceof OrderedMapNode) { + writer.startOrderedMapNode(((OrderedMapNode) node).getIdentifier(), UNKNOWN_SIZE); + hasDataContainerChild = true; + } else if (node instanceof MapNode) { + writer.startMapNode(((MapNode) node).getIdentifier(), UNKNOWN_SIZE); + hasDataContainerChild = true; + //covers also OrderedLeafSetNode for which doesn't exist start* method + } else if (node instanceof LeafSetNode) { + writer.startLeafSet(((LeafSetNode) node).getIdentifier(), UNKNOWN_SIZE); + hasDataContainerChild = true; + } + + if (hasDataContainerChild) { + for (NormalizedNode childNode : ((NormalizedNode>>) node).getValue()) { + write(childNode); + } + + writer.endNode(); + return true; + } + return false; + + } + + @Override + public void flush() throws IOException { + writer.flush(); + } + + @Override + public void close() throws IOException { + writer.close(); + } +} diff --git a/yang/yang-data-codec-gson/pom.xml b/yang/yang-data-codec-gson/pom.xml new file mode 100644 index 0000000000..c8e1719906 --- /dev/null +++ b/yang/yang-data-codec-gson/pom.xml @@ -0,0 +1,47 @@ + + + + + + org.opendaylight.yangtools + yangtools-parent + 0.6.2-SNAPSHOT + /../../common/parent/pom.xml + + + 4.0.0 + yang-data-codec-gson + ${project.artifactId} + ${project.artifactId} + + + + org.opendaylight.yangtools + yang-data-api + + + com.google.code.gson + gson + + + org.opendaylight.yangtools + yang-data-impl + + + org.opendaylight.yangtools + yang-parser-impl + test + + + junit + junit + test + + + diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/AbstractNodeDataWithSchema.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/AbstractNodeDataWithSchema.java new file mode 100644 index 0000000000..3284df2cdd --- /dev/null +++ b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/AbstractNodeDataWithSchema.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2014 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.io.IOException; + +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; + +abstract class AbstractNodeDataWithSchema { + + private final DataSchemaNode schema; + + public AbstractNodeDataWithSchema(final DataSchemaNode schema) { + this.schema = schema; + } + + public final DataSchemaNode getSchema() { + return schema; + } + + protected abstract void writeToStream(final NormalizedNodeStreamWriter nnStreamWriter) throws IOException; + + protected NodeIdentifier provideNodeIdentifier() { + return new NodeIdentifier(schema.getQName()); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((schema == null) ? 0 : schema.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + AbstractNodeDataWithSchema other = (AbstractNodeDataWithSchema) obj; + if (schema == null) { + if (other.schema != null) { + return false; + } + } else if (!schema.equals(other.schema)) { + return false; + } + + return true; + } + +} diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/AnyXmlNodeDataWithSchema.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/AnyXmlNodeDataWithSchema.java new file mode 100644 index 0000000000..aaef749ffa --- /dev/null +++ b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/AnyXmlNodeDataWithSchema.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014 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.io.IOException; + +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; + +class AnyXmlNodeDataWithSchema extends SimpleNodeDataWithSchema { + + public AnyXmlNodeDataWithSchema(final DataSchemaNode dataSchemaNode) { + super(dataSchemaNode); + } + + @Override + protected void writeToStream(final NormalizedNodeStreamWriter nnStreamWriter) throws IOException { +// FIXME: should be changed according to format of value + nnStreamWriter.anyxmlNode(provideNodeIdentifier(), getValue()); + } +} diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/CaseNodeDataWithSchema.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/CaseNodeDataWithSchema.java new file mode 100644 index 0000000000..e3fdaede26 --- /dev/null +++ b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/CaseNodeDataWithSchema.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2014 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.io.IOException; + +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode; + +class CaseNodeDataWithSchema extends CompositeNodeDataWithSchema { + + public CaseNodeDataWithSchema(final ChoiceCaseNode schema) { + super(schema); + } + + @Override + protected void writeToStream(final NormalizedNodeStreamWriter nnStreamWriter) throws IOException { + super.writeToStream(nnStreamWriter); + } +} diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/ChoiceNodeDataWithSchema.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/ChoiceNodeDataWithSchema.java new file mode 100644 index 0000000000..39c1589053 --- /dev/null +++ b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/ChoiceNodeDataWithSchema.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2014 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 static org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter.UNKNOWN_SIZE; + +import java.io.IOException; + +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode; +import org.opendaylight.yangtools.yang.model.api.ChoiceNode; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; + +/** + * + * childs - empty augment - only one element can be + * + */ +class ChoiceNodeDataWithSchema extends CompositeNodeDataWithSchema { + + private CaseNodeDataWithSchema caseNodeDataWithSchema; + + public ChoiceNodeDataWithSchema(final ChoiceNode schema) { + super(schema); + } + + @Override + public CompositeNodeDataWithSchema addCompositeChild(final DataSchemaNode schema) { + CaseNodeDataWithSchema newChild = new CaseNodeDataWithSchema((ChoiceCaseNode) schema); + caseNodeDataWithSchema = newChild; + addCompositeChild(newChild); + return newChild; + } + + public CaseNodeDataWithSchema getCase() { + return caseNodeDataWithSchema; + } + + @Override + protected void writeToStream(final NormalizedNodeStreamWriter nnStreamWriter) throws IOException { + nnStreamWriter.startChoiceNode(provideNodeIdentifier(), UNKNOWN_SIZE); + super.writeToStream(nnStreamWriter); + nnStreamWriter.endNode(); + } + +} diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/CompositeNodeDataWithSchema.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/CompositeNodeDataWithSchema.java new file mode 100644 index 0000000000..83d715cefe --- /dev/null +++ b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/CompositeNodeDataWithSchema.java @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2014 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.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode; +import org.opendaylight.yangtools.yang.model.api.AugmentationSchema; +import org.opendaylight.yangtools.yang.model.api.AugmentationTarget; +import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode; +import org.opendaylight.yangtools.yang.model.api.ChoiceNode; +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; +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.ListSchemaNode; + +class CompositeNodeDataWithSchema extends AbstractNodeDataWithSchema { + + /** + * nodes which were added to schema via augmentation and are present in data input + */ + protected Map> augmentationsToChild = new HashMap<>(); + + /** + * remaining data nodes (which aren't added via augment). Every of them should have the same QName + */ + protected List childs = new ArrayList<>(); + + public CompositeNodeDataWithSchema(final DataSchemaNode schema) { + super(schema); + } + + public AbstractNodeDataWithSchema addSimpleChild(final DataSchemaNode schema) { + SimpleNodeDataWithSchema newChild = null; + if (schema instanceof LeafSchemaNode) { + newChild = new LeafNodeDataWithSchema(schema); + } else if (schema instanceof AnyXmlSchemaNode) { + newChild = new AnyXmlNodeDataWithSchema(schema); + } + + if (newChild != null) { + + AugmentationSchema augSchema = null; + if (schema.isAugmenting()) { + augSchema = findCorrespondingAugment(getSchema(), schema); + } + if (augSchema != null) { + addChildToAugmentation(augSchema, newChild); + } else { + addChild(newChild); + } + return newChild; + } + return null; + } + + private void addChildToAugmentation(final AugmentationSchema augSchema, final AbstractNodeDataWithSchema newChild) { + List childsInAugment = augmentationsToChild.get(augSchema); + if (childsInAugment == null) { + childsInAugment = new ArrayList<>(); + augmentationsToChild.put(augSchema, childsInAugment); + } + childsInAugment.add(newChild); + } + + public AbstractNodeDataWithSchema addChild(final Deque schemas) { + if (schemas.size() == 1) { + final DataSchemaNode childDataSchemaNode = schemas.pop(); + return addChild(childDataSchemaNode); + } else { + DataSchemaNode choiceCandidate = schemas.pop(); + DataSchemaNode caseCandidate = schemas.pop(); + ChoiceNode choiceNode = null; + ChoiceCaseNode caseNode = null; + if (choiceCandidate instanceof ChoiceNode) { + choiceNode = (ChoiceNode) choiceCandidate; + } else { + throw new IllegalArgumentException("Awaited node of type ChoiceNode but was " + + choiceCandidate.getClass().getSimpleName()); + } + + if (caseCandidate instanceof ChoiceCaseNode) { + caseNode = (ChoiceCaseNode) caseCandidate; + } else { + throw new IllegalArgumentException("Awaited node of type ChoiceCaseNode but was " + + caseCandidate.getClass().getSimpleName()); + } + + AugmentationSchema augSchema = null; + if (choiceCandidate.isAugmenting()) { + augSchema = findCorrespondingAugment(getSchema(), choiceCandidate); + } + + // looking for existing choice + List childNodes = Collections.emptyList(); + if (augSchema != null) { + childNodes = augmentationsToChild.get(augSchema); + } else { + childNodes = childs; + } + + CompositeNodeDataWithSchema caseNodeDataWithSchema = findChoice(childNodes, choiceCandidate, caseCandidate); + if (caseNodeDataWithSchema == null) { + ChoiceNodeDataWithSchema choiceNodeDataWithSchema = new ChoiceNodeDataWithSchema(choiceNode); + addChild(choiceNodeDataWithSchema); + caseNodeDataWithSchema = choiceNodeDataWithSchema.addCompositeChild(caseNode); + } + + return caseNodeDataWithSchema.addChild(schemas); + } + + } + + private CaseNodeDataWithSchema findChoice(final List childNodes, final DataSchemaNode choiceCandidate, + final DataSchemaNode caseCandidate) { + if (childNodes == null) { + return null; + } + for (AbstractNodeDataWithSchema nodeDataWithSchema : childNodes) { + if (nodeDataWithSchema instanceof ChoiceNodeDataWithSchema + && nodeDataWithSchema.getSchema().getQName().equals(choiceCandidate.getQName())) { + CaseNodeDataWithSchema casePrevious = ((ChoiceNodeDataWithSchema) nodeDataWithSchema).getCase(); + if (casePrevious.getSchema().getQName() != caseCandidate.getQName()) { + throw new IllegalArgumentException("Data from case " + caseCandidate.getQName() + + " are specified but other data from case " + casePrevious.getSchema().getQName() + + " were specified erlier. Data aren't from the same case."); + } + return casePrevious; + } + } + return null; + } + + public AbstractNodeDataWithSchema addCompositeChild(final DataSchemaNode schema) { + CompositeNodeDataWithSchema newChild; + if (schema instanceof ListSchemaNode) { + newChild = new ListNodeDataWithSchema(schema); + } else if (schema instanceof LeafListSchemaNode) { + newChild = new LeafListNodeDataWithSchema(schema); + } else if (schema instanceof ContainerSchemaNode) { + newChild = new ContainerNodeDataWithSchema(schema); + } else { + newChild = new CompositeNodeDataWithSchema(schema); + } + addCompositeChild(newChild); + return newChild; + } + + public void addCompositeChild(final CompositeNodeDataWithSchema newChild) { + AugmentationSchema augSchema = findCorrespondingAugment(getSchema(), newChild.getSchema()); + if (augSchema != null) { + addChildToAugmentation(augSchema, newChild); + } else { + addChild(newChild); + } + } + + private AbstractNodeDataWithSchema addChild(final DataSchemaNode schema) { + AbstractNodeDataWithSchema newChild = addSimpleChild(schema); + return newChild == null ? addCompositeChild(schema) : newChild; + } + + public void addChild(final AbstractNodeDataWithSchema newChild) { + childs.add(newChild); + } + + /** + * Tries to find in {@code parent} which is dealed as augmentation target node with QName as {@code child}. If such + * node is found then it is returned, else null. + */ + protected AugmentationSchema findCorrespondingAugment(final DataSchemaNode parent, final DataSchemaNode child) { + if (parent instanceof AugmentationTarget) { + for (AugmentationSchema augmentation : ((AugmentationTarget) parent).getAvailableAugmentations()) { + DataSchemaNode childInAugmentation = augmentation.getDataChildByName(child.getQName()); + if (childInAugmentation != null) { + return augmentation; + } + } + } + return null; + } + + @Override + protected void writeToStream(final NormalizedNodeStreamWriter nnStreamWriter) throws IOException { + for (AbstractNodeDataWithSchema child : childs) { + child.writeToStream(nnStreamWriter); + } + for (Entry> augmentationToChild : augmentationsToChild.entrySet()) { + + final List childsFromAgumentation = augmentationToChild.getValue(); + + if (!childsFromAgumentation.isEmpty()) { + nnStreamWriter.startAugmentationNode(toAugmentationIdentifier(augmentationToChild)); + + for (AbstractNodeDataWithSchema nodeDataWithSchema : childsFromAgumentation) { + nodeDataWithSchema.writeToStream(nnStreamWriter); + } + + nnStreamWriter.endNode(); + } + } + } + + private static AugmentationIdentifier toAugmentationIdentifier( + final Entry> augmentationToChild) { + Collection nodes = augmentationToChild.getKey().getChildNodes(); + Set nodesQNames = new HashSet<>(); + for (DataSchemaNode node : nodes) { + nodesQNames.add(node.getQName()); + } + + return new AugmentationIdentifier(nodesQNames); + } + +} diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/ContainerNodeDataWithSchema.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/ContainerNodeDataWithSchema.java new file mode 100644 index 0000000000..c49d71b6fc --- /dev/null +++ b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/ContainerNodeDataWithSchema.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2014 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 static org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter.UNKNOWN_SIZE; + +import java.io.IOException; + +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; + +class ContainerNodeDataWithSchema extends CompositeNodeDataWithSchema { + + public ContainerNodeDataWithSchema(final DataSchemaNode schema) { + super(schema); + } + + @Override + protected void writeToStream(final NormalizedNodeStreamWriter nnStreamWriter) throws IOException { + nnStreamWriter.startContainerNode(provideNodeIdentifier(), UNKNOWN_SIZE); + super.writeToStream(nnStreamWriter); + nnStreamWriter.endNode(); + } + +} diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONNormalizedNodeStreamWriter.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONNormalizedNodeStreamWriter.java new file mode 100644 index 0000000000..16428cd477 --- /dev/null +++ b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONNormalizedNodeStreamWriter.java @@ -0,0 +1,314 @@ +/* + * Copyright (c) 2014 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 com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import com.google.gson.stream.JsonWriter; + +import java.io.IOException; +import java.io.Writer; +import java.net.URI; +import java.util.ArrayDeque; +import java.util.Deque; + +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; + +/** + * This implementation will create JSON output as output stream. + * + * Values of leaf and leaf-list are NOT translated according to codecs. + * + * FIXME: rewrite this in terms of {@link JsonWriter}. + */ +public class JSONNormalizedNodeStreamWriter implements NormalizedNodeStreamWriter { + + private static enum NodeType { + OBJECT, + LIST, + OTHER, + } + + private static class TypeInfo { + private boolean hasAtLeastOneChild = false; + private final NodeType type; + private final URI uri; + + public TypeInfo(final NodeType type, final URI uri) { + this.type = type; + this.uri = uri; + } + + public void setHasAtLeastOneChild(final boolean hasChildren) { + this.hasAtLeastOneChild = hasChildren; + } + + public NodeType getType() { + return type; + } + + public URI getNamespace() { + return uri; + } + + public boolean hasAtLeastOneChild() { + return hasAtLeastOneChild; + } + } + + private final Deque stack = new ArrayDeque<>(); + private final SchemaContext schemaContext; + private final Writer writer; + private final String indent; + + private URI currentNamespace = null; + private int currentDepth = 0; + + private JSONNormalizedNodeStreamWriter(final SchemaContext schemaContext, + final Writer writer, final int indentSize) { + this.schemaContext = Preconditions.checkNotNull(schemaContext); + this.writer = Preconditions.checkNotNull(writer); + + Preconditions.checkArgument(indentSize >= 0, "Indent size must be non-negative"); + + if (indentSize != 0) { + indent = Strings.repeat(" ", indentSize); + } else { + indent = null; + } + } + + /** + * Create a new stream writer, which writes to the specified {@link Writer}. + * + * @param schemaContext Schema context + * @param writer Output writer + * @return A stream writer instance + */ + public static NormalizedNodeStreamWriter create(final SchemaContext schemaContext, final Writer writer) { + return new JSONNormalizedNodeStreamWriter(schemaContext, writer, 0); + } + + /** + * Create a new stream writer, which writes to the specified output stream. + * + * @param schemaContext Schema context + * @param writer Output writer + * @param indentSize indentation size + * @return A stream writer instance + */ + public static NormalizedNodeStreamWriter create(final SchemaContext schemaContext, final Writer writer, final int indentSize) { + return new JSONNormalizedNodeStreamWriter(schemaContext, writer, indentSize); + } + + @Override + public void leafNode(final NodeIdentifier name, final Object value) throws IOException { + separateElementFromPreviousElement(); + writeJsonIdentifier(name); + currentNamespace = stack.peek().getNamespace(); + writeValue(value.toString()); + separateNextSiblingsWithComma(); + } + + @Override + public void startLeafSet(final NodeIdentifier name, final int childSizeHint) throws IOException { + separateElementFromPreviousElement(); + stack.push(new TypeInfo(NodeType.LIST, name.getNodeType().getNamespace())); + writeJsonIdentifier(name); + writeStartList(); + indentRight(); + } + + @Override + public void leafSetEntryNode(final Object value) throws IOException { + separateElementFromPreviousElement(); + writeValue(value.toString()); + separateNextSiblingsWithComma(); + } + + @Override + public void startContainerNode(final NodeIdentifier name, final int childSizeHint) throws IOException { + separateElementFromPreviousElement(); + stack.push(new TypeInfo(NodeType.OBJECT, name.getNodeType().getNamespace())); + writeJsonIdentifier(name); + writeStartObject(); + indentRight(); + } + + @Override + public void startUnkeyedList(final NodeIdentifier name, final int childSizeHint) throws IOException { + separateElementFromPreviousElement(); + stack.push(new TypeInfo(NodeType.LIST, name.getNodeType().getNamespace())); + writeJsonIdentifier(name); + writeStartList(); + indentRight(); + } + + @Override + public void startUnkeyedListItem(final NodeIdentifier name, final int childSizeHint) throws IOException { + stack.push(new TypeInfo(NodeType.OBJECT, name.getNodeType().getNamespace())); + separateElementFromPreviousElement(); + writeStartObject(); + indentRight(); + } + + @Override + public void startMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException { + separateElementFromPreviousElement(); + stack.push(new TypeInfo(NodeType.LIST, name.getNodeType().getNamespace())); + writeJsonIdentifier(name); + writeStartList(); + indentRight(); + } + + @Override + public void startMapEntryNode(final NodeIdentifierWithPredicates identifier, final int childSizeHint) + throws IOException { + stack.push(new TypeInfo(NodeType.OBJECT, identifier.getNodeType().getNamespace())); + separateElementFromPreviousElement(); + writeStartObject(); + indentRight(); + } + + @Override + public void startOrderedMapNode(final NodeIdentifier name, final int childSizeHint) throws IOException { + stack.push(new TypeInfo(NodeType.LIST, name.getNodeType().getNamespace())); + separateElementFromPreviousElement(); + writeJsonIdentifier(name); + writeStartList(); + indentRight(); + } + + @Override + public void startChoiceNode(final NodeIdentifier name, final int childSizeHint) throws IllegalArgumentException { + handleInvisibleNode(name.getNodeType().getNamespace()); + } + + @Override + public void startAugmentationNode(final AugmentationIdentifier identifier) throws IllegalArgumentException { + handleInvisibleNode(currentNamespace); + } + + @Override + public void anyxmlNode(final NodeIdentifier name, final Object value) throws IOException { + separateElementFromPreviousElement(); + writeJsonIdentifier(name); + currentNamespace = stack.peek().getNamespace(); + writeValue(value.toString()); + separateNextSiblingsWithComma(); + } + + @Override + public void endNode() throws IOException { + switch (stack.peek().getType()) { + case LIST: + indentLeft(); + newLine(); + writer.append(']'); + break; + case OBJECT: + indentLeft(); + newLine(); + writer.append('}'); + break; + default: + break; + } + stack.pop(); + currentNamespace = stack.isEmpty() ? null : stack.peek().getNamespace(); + separateNextSiblingsWithComma(); + } + + private void separateElementFromPreviousElement() throws IOException { + if (!stack.isEmpty() && stack.peek().hasAtLeastOneChild()) { + writer.append(','); + } + newLine(); + } + + private void newLine() throws IOException { + if (indent != null) { + writer.append('\n'); + + for (int i = 0; i < currentDepth; i++) { + writer.append(indent); + } + } + } + + private void separateNextSiblingsWithComma() { + if (!stack.isEmpty()) { + stack.peek().setHasAtLeastOneChild(true); + } + } + + /** + * Invisible nodes have to be also pushed to stack because of pairing of start*() and endNode() methods. Information + * about child existing (due to printing comma) has to be transfered to invisible node. + */ + private void handleInvisibleNode(final URI uri) { + TypeInfo typeInfo = new TypeInfo(NodeType.OTHER, uri); + typeInfo.setHasAtLeastOneChild(stack.peek().hasAtLeastOneChild()); + stack.push(typeInfo); + } + + private void writeStartObject() throws IOException { + writer.append('{'); + } + + private void writeStartList() throws IOException { + writer.append('['); + } + + private void writeModulName(final URI namespace) throws IOException { + if (this.currentNamespace == null || namespace != this.currentNamespace) { + Module module = schemaContext.findModuleByNamespaceAndRevision(namespace, null); + writer.append(module.getName()); + writer.append(':'); + currentNamespace = namespace; + } + } + + private void writeValue(final String value) throws IOException { + writer.append('"'); + writer.append(value); + writer.append('"'); + } + + private void writeJsonIdentifier(final NodeIdentifier name) throws IOException { + writer.append('"'); + writeModulName(name.getNodeType().getNamespace()); + writer.append(name.getNodeType().getLocalName()); + writer.append("\":"); + } + + private void indentRight() { + currentDepth++; + } + + private void indentLeft() { + currentDepth--; + } + + @Override + public void flush() throws IOException { + writer.flush(); + } + + @Override + public void close() throws IOException { + writer.flush(); + writer.close(); + } + +} 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 new file mode 100644 index 0000000000..18232ff77e --- /dev/null +++ b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JsonParserStream.java @@ -0,0 +1,388 @@ +/* + * Copyright (c) 2014 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 com.google.common.annotations.Beta; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.base.Splitter; +import com.google.common.collect.Iterators; +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.JsonToken; +import com.google.gson.stream.MalformedJsonException; + +import java.io.Closeable; +import java.io.EOFException; +import java.io.Flushable; +import java.io.IOException; +import java.net.URI; +import java.security.InvalidParameterException; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Deque; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import org.opendaylight.yangtools.concepts.Codec; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.data.codec.gson.helpers.IdentityValuesDTO; +import org.opendaylight.yangtools.yang.data.codec.gson.helpers.RestCodecFactory; +import org.opendaylight.yangtools.yang.data.codec.gson.helpers.RestUtil; +import org.opendaylight.yangtools.yang.data.codec.gson.helpers.RestUtil.PrefixMapingFromJson; +import org.opendaylight.yangtools.yang.data.codec.gson.helpers.SchemaContextUtils; +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.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.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.TypeDefinition; +import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition; +import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition; + +/** + * This class parses JSON elements from a GSON JsonReader. It disallows multiple elements of the same name unlike the + * default GSON JsonParser. + */ +@Beta +public final class JsonParserStream implements Closeable, Flushable { + private static final Splitter COLON_SPLITTER = Splitter.on(':'); + + private final Deque namespaces = new ArrayDeque<>(); + private final NormalizedNodeStreamWriter writer; + private final SchemaContextUtils utils; + private final RestCodecFactory codecs; + private final SchemaContext schema; + + private JsonParserStream(final NormalizedNodeStreamWriter writer, final SchemaContext schemaContext) { + this.schema = Preconditions.checkNotNull(schemaContext); + this.utils = SchemaContextUtils.create(schemaContext); + this.writer = Preconditions.checkNotNull(writer); + this.codecs = RestCodecFactory.create(utils); + } + + public static JsonParserStream create(final NormalizedNodeStreamWriter writer, final SchemaContext schemaContext) { + return new JsonParserStream(writer, schemaContext); + } + + public JsonParserStream parse(final JsonReader reader) throws JsonIOException, JsonSyntaxException { + // code copied from gson's JsonParser and Stream classes + + boolean lenient = reader.isLenient(); + reader.setLenient(true); + boolean isEmpty = true; + try { + reader.peek(); + isEmpty = false; + CompositeNodeDataWithSchema compositeNodeDataWithSchema = new CompositeNodeDataWithSchema(schema); + read(reader, compositeNodeDataWithSchema); + compositeNodeDataWithSchema.writeToStream(writer); + + return this; + // return read(reader); + } catch (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) { + throw new JsonSyntaxException(e); + } catch (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 { + reader.setLenient(lenient); + } + } + + public void read(final JsonReader in, final AbstractNodeDataWithSchema parent) throws IOException { + + final JsonToken peek = in.peek(); + Optional value = Optional.absent(); + switch (peek) { + case STRING: + case NUMBER: + value = Optional.of(in.nextString()); + break; + case BOOLEAN: + value = Optional.of(Boolean.toString(in.nextBoolean())); + break; + case NULL: + in.nextNull(); + value = Optional.of((String) null); + break; + default: + break; + } + if (value.isPresent()) { + final Object translatedValue = translateValueByType(value.get(), parent.getSchema()); + ((SimpleNodeDataWithSchema) parent).setValue(translatedValue); + } + + switch (peek) { + 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); + } + read(in, newChild); + } + in.endArray(); + return; + case BEGIN_OBJECT: + Set namesakes = new HashSet<>(); + in.beginObject(); + while (in.hasNext()) { + final String jsonElementName = in.nextName(); + final NamespaceAndName namespaceAndName = resolveNamespace(jsonElementName); + 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(), + 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 + if (newChild instanceof AnyXmlNodeDataWithSchema) { + in.skipValue(); + } else { + read(in, newChild); + } + removeNamespace(); + } + in.endObject(); + return; + case END_DOCUMENT: + case NAME: + case END_OBJECT: + case END_ARRAY: + } + } + + private Object translateValueByType(final String value, final DataSchemaNode node) { + final TypeDefinition typeDefinition = typeDefinition(node); + if (typeDefinition == null) { + return value; + } + + final Object inputValue; + if (typeDefinition instanceof IdentityrefTypeDefinition) { + inputValue = valueAsIdentityRef(value); + } else if (typeDefinition instanceof InstanceIdentifierTypeDefinition) { + inputValue = valueAsInstanceIdentifier(value); + } else { + inputValue = value; + } + + // FIXME: extract this as a cacheable context? + final Codec codec = codecs.codecFor(typeDefinition); + if (codec == null) { + return null; + } + return codec.deserialize(inputValue); + } + + 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; + } else { + throw new IllegalArgumentException("Unhandled parameter types: " + Arrays. asList(node).toString()); + } + + if (baseType != null) { + while (baseType.getBaseType() != null) { + baseType = baseType.getBaseType(); + } + } + return baseType; + } + + private static Object valueAsInstanceIdentifier(final String value) { + // it could be instance-identifier Built-In Type + if (!value.isEmpty() && value.charAt(0) == '/') { + IdentityValuesDTO resolvedValue = RestUtil.asInstanceIdentifier(value, new PrefixMapingFromJson()); + if (resolvedValue != null) { + return resolvedValue; + } + } + throw new InvalidParameterException("Value for instance-identifier doesn't have correct format"); + } + + private static IdentityValuesDTO valueAsIdentityRef(final String value) { + // it could be identityref Built-In Type + URI namespace = getNamespaceFor(value); + if (namespace != null) { + return new IdentityValuesDTO(namespace.toString(), getLocalNameFor(value), null, value); + } + throw new InvalidParameterException("Value for identityref has to be in format moduleName:localName."); + } + + private static URI getNamespaceFor(final String jsonElementName) { + final Iterator it = COLON_SPLITTER.split(jsonElementName).iterator(); + + // The string needs to me in form "moduleName:localName" + if (it.hasNext()) { + final String maybeURI = it.next(); + if (Iterators.size(it) == 1) { + return URI.create(maybeURI); + } + } + + return null; + } + + private static String getLocalNameFor(final String jsonElementName) { + final Iterator it = COLON_SPLITTER.split(jsonElementName).iterator(); + + // The string needs to me in form "moduleName:localName" + final String ret = Iterators.get(it, 1, null); + return ret != null && !it.hasNext() ? ret : jsonElementName; + } + + 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 NamespaceAndName resolveNamespace(final String childName) { + int lastIndexOfColon = childName.lastIndexOf(":"); + String moduleNamePart = null; + String nodeNamePart = null; + URI namespace = null; + if (lastIndexOfColon != -1) { + moduleNamePart = childName.substring(0, lastIndexOfColon); + nodeNamePart = childName.substring(lastIndexOfColon + 1); + namespace = utils.findNamespaceByModuleName(moduleNamePart); + } else { + nodeNamePart = childName; + } + + Optional namespaceOpt = namespace == null ? Optional. absent() : Optional.of(namespace); + return new NamespaceAndName(nodeNamePart, namespaceOpt); + } + + private URI getCurrentNamespace() { + return namespaces.peek(); + } + + /** + * Returns stack of schema nodes via which it was necessary to prass to get schema node with specified + * {@code childName} and {@code namespace} + * + * @param dataSchemaNode + * @param childName + * @param namespace + * @return stack of schema nodes via which it was passed through. If found schema node is dirrect child then stack + * contains only one node. If it is found under choice and case then stack should conains 2*n+1 element + * (where n is number of choices through it was passed) + */ + private Deque findSchemaNodeByNameAndNamespace(final DataSchemaNode dataSchemaNode, + final String childName, final URI namespace) { + final Deque result = new ArrayDeque<>(); + List childChoices = new ArrayList<>(); + if (dataSchemaNode instanceof DataNodeContainer) { + for (DataSchemaNode childNode : ((DataNodeContainer) dataSchemaNode).getChildNodes()) { + if (childNode instanceof ChoiceNode) { + childChoices.add((ChoiceNode) childNode); + } else { + final QName childQName = childNode.getQName(); + if (childQName.getLocalName().equals(childName) && childQName.getNamespace().equals(namespace)) { + result.push(childNode); + 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, + namespace); + if (!resultFromRecursion.isEmpty()) { + resultFromRecursion.push(concreteCase); + resultFromRecursion.push(choiceNode); + return resultFromRecursion; + } + } + } + return result; + } + + private static class NamespaceAndName { + private final Optional uri; + private final String name; + + public NamespaceAndName(final String name, final Optional uri) { + this.name = name; + this.uri = uri; + } + + public String getName() { + return name; + } + + public Optional getUri() { + return uri; + } + } + + @Override + public void flush() throws IOException { + writer.flush(); + } + + @Override + public void close() throws IOException { + writer.flush(); + writer.close(); + } +} diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/LeafListEntryNodeDataWithSchema.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/LeafListEntryNodeDataWithSchema.java new file mode 100644 index 0000000000..e53367c637 --- /dev/null +++ b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/LeafListEntryNodeDataWithSchema.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2014 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.io.IOException; + +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; + +class LeafListEntryNodeDataWithSchema extends SimpleNodeDataWithSchema { + public LeafListEntryNodeDataWithSchema(final DataSchemaNode dataSchemaNode) { + super(dataSchemaNode); + } + + @Override + protected void writeToStream(final NormalizedNodeStreamWriter nnStreamWriter) throws IOException { + nnStreamWriter.leafSetEntryNode(getValue()); + } +} diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/LeafListNodeDataWithSchema.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/LeafListNodeDataWithSchema.java new file mode 100644 index 0000000000..8b23acdb9f --- /dev/null +++ b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/LeafListNodeDataWithSchema.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2014 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 static org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter.UNKNOWN_SIZE; + +import java.io.IOException; + +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; + +class LeafListNodeDataWithSchema extends CompositeNodeDataWithSchema { + public LeafListNodeDataWithSchema(final DataSchemaNode schema) { + super(schema); + } + + @Override + protected void writeToStream(final NormalizedNodeStreamWriter nnStreamWriter) throws IOException { + nnStreamWriter.startLeafSet(provideNodeIdentifier(), UNKNOWN_SIZE); + super.writeToStream(nnStreamWriter); + nnStreamWriter.endNode(); + } +} diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/LeafNodeDataWithSchema.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/LeafNodeDataWithSchema.java new file mode 100644 index 0000000000..c63422af73 --- /dev/null +++ b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/LeafNodeDataWithSchema.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014 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.io.IOException; + +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; + +class LeafNodeDataWithSchema extends SimpleNodeDataWithSchema { + + public LeafNodeDataWithSchema(final DataSchemaNode schema) { + super(schema); + } + + @Override + protected void writeToStream(final NormalizedNodeStreamWriter nnStreamWriter) throws IOException { + nnStreamWriter.leafNode(provideNodeIdentifier(), getValue()); + } + +} diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/ListEntryNodeDataWithSchema.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/ListEntryNodeDataWithSchema.java new file mode 100644 index 0000000000..b08add8a36 --- /dev/null +++ b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/ListEntryNodeDataWithSchema.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2014 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 static org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter.UNKNOWN_SIZE; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; +import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; + +class ListEntryNodeDataWithSchema extends CompositeNodeDataWithSchema { + + private final Map qNameToKeys = new HashMap<>(); + + public ListEntryNodeDataWithSchema(final DataSchemaNode schema) { + super(schema); + } + + @Override + public void addChild(final AbstractNodeDataWithSchema newChild) { + DataSchemaNode childSchema = newChild.getSchema(); + if (childSchema instanceof LeafSchemaNode && isPartOfKey((LeafSchemaNode) childSchema)) { + qNameToKeys.put(childSchema.getQName(), (SimpleNodeDataWithSchema)newChild); + } + super.addChild(newChild); + } + + private boolean isPartOfKey(final LeafSchemaNode potentialKey) { + List keys = ((ListSchemaNode) getSchema()).getKeyDefinition(); + for (QName qName : keys) { + if (qName.equals(potentialKey.getQName())) { + return true; + } + } + return false; + } + + @Override + protected void writeToStream(final NormalizedNodeStreamWriter nnStreamWriter) throws IOException { + int keyCount = ((ListSchemaNode) getSchema()).getKeyDefinition().size(); + if (keyCount == 0) { + nnStreamWriter.startUnkeyedListItem(provideNodeIdentifier(), UNKNOWN_SIZE); + super.writeToStream(nnStreamWriter); + nnStreamWriter.endNode(); + } else if (keyCount == qNameToKeys.size()) { + nnStreamWriter.startMapEntryNode(provideNodeIdentifierWithPredicates(), UNKNOWN_SIZE); + super.writeToStream(nnStreamWriter); + nnStreamWriter.endNode(); + } else { + throw new IllegalStateException("Some of keys of " + getSchema().getQName() + " are missing in input."); + } + } + + private NodeIdentifierWithPredicates provideNodeIdentifierWithPredicates() { + Map qNameToPredicateValues = new HashMap<>(); + + for (SimpleNodeDataWithSchema simpleNodeDataWithSchema : qNameToKeys.values()) { + qNameToPredicateValues.put(simpleNodeDataWithSchema.getSchema().getQName(), simpleNodeDataWithSchema.getValue()); + } + + return new NodeIdentifierWithPredicates(getSchema().getQName(), qNameToPredicateValues); + } + +} diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/ListNodeDataWithSchema.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/ListNodeDataWithSchema.java new file mode 100644 index 0000000000..612c386e23 --- /dev/null +++ b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/ListNodeDataWithSchema.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2014 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 static org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter.UNKNOWN_SIZE; + +import java.io.IOException; + +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; + +class ListNodeDataWithSchema extends CompositeNodeDataWithSchema { + + public ListNodeDataWithSchema(final DataSchemaNode schema) { + super(schema); + } + + @Override + protected void writeToStream(final NormalizedNodeStreamWriter nnStreamWriter) throws IOException { + if (!((ListSchemaNode) getSchema()).getKeyDefinition().isEmpty()) { + nnStreamWriter.startMapNode(provideNodeIdentifier(), UNKNOWN_SIZE); + } else { + nnStreamWriter.startUnkeyedList(provideNodeIdentifier(), UNKNOWN_SIZE); + } + super.writeToStream(nnStreamWriter); + nnStreamWriter.endNode(); + } + +} diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/SimpleNodeDataWithSchema.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/SimpleNodeDataWithSchema.java new file mode 100644 index 0000000000..26d774cde2 --- /dev/null +++ b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/SimpleNodeDataWithSchema.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2014 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 org.opendaylight.yangtools.yang.model.api.DataSchemaNode; + +abstract class SimpleNodeDataWithSchema extends AbstractNodeDataWithSchema { + + private Object value; + + public SimpleNodeDataWithSchema(final DataSchemaNode dataSchemaNode) { + super(dataSchemaNode); + } + + void setValue(final Object value) { + this.value = value; + } + + public Object getValue() { + return value; + } + +} diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/AbstractCodecImpl.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/AbstractCodecImpl.java new file mode 100644 index 0000000000..407d8661ab --- /dev/null +++ b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/AbstractCodecImpl.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2014 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.helpers; + +import com.google.common.base.Preconditions; + +import java.net.URI; + +import org.opendaylight.yangtools.yang.model.api.Module; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +abstract class AbstractCodecImpl { + private static final Logger LOG = LoggerFactory.getLogger(AbstractCodecImpl.class); + private final SchemaContextUtils schema; + + protected AbstractCodecImpl(final SchemaContextUtils schema) { + this.schema = Preconditions.checkNotNull(schema); + } + + protected final SchemaContextUtils getSchema() { + return schema; + } + + protected final Module getModuleByNamespace(final String namespace) { + URI validNamespace = resolveValidNamespace(namespace); + + Module module = schema.findModuleByNamespace(validNamespace); + if (module == null) { + LOG.info("Module for namespace " + validNamespace + " wasn't found."); + return null; + } + return module; + } + + protected final URI resolveValidNamespace(final String namespace) { + URI validNamespace = schema.findNamespaceByModuleName(namespace); + if (validNamespace == null) { + validNamespace = URI.create(namespace); + } + + return validNamespace; + } +} diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/IdentityValuesDTO.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/IdentityValuesDTO.java new file mode 100644 index 0000000000..30ba2a0f9c --- /dev/null +++ b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/IdentityValuesDTO.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2014 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.helpers; + +import com.google.common.annotations.Beta; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * This class is implementation-internal and subject to change. Please do not use it. + */ +@Beta +public final class IdentityValuesDTO { + + private final List elementData = new ArrayList<>(); + private final String originValue; + + public IdentityValuesDTO(final String namespace, final String value, final String prefix, final String originValue) { + elementData.add(new IdentityValue(namespace, value, prefix)); + this.originValue = originValue; + } + + public IdentityValuesDTO(final String originValue) { + this.originValue = originValue; + } + + public IdentityValuesDTO() { + originValue = null; + } + + public void add(final String namespace, final String value, final String prefix) { + elementData.add(new IdentityValue(namespace, value, prefix)); + } + + public void add(final IdentityValue identityValue) { + elementData.add(identityValue); + } + + public List getValuesWithNamespaces() { + return Collections.unmodifiableList(elementData); + } + + @Override + public String toString() { + return elementData.toString(); + } + + public String getOriginValue() { + return originValue; + } + + public static final class IdentityValue { + + private final String namespace; + private final String value; + private final String prefix; + private List predicates; + + public IdentityValue(final String namespace, final String value, final String prefix) { + this.namespace = namespace; + this.value = value; + this.prefix = prefix; + } + + public String getNamespace() { + return namespace; + } + + public String getValue() { + return value; + } + + public String getPrefix() { + return prefix; + } + + public List getPredicates() { + if (predicates == null) { + return Collections.emptyList(); + } + return Collections.unmodifiableList(predicates); + } + + public void setPredicates(final List predicates) { + this.predicates = predicates; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + if (namespace != null) { + sb.append(namespace); + } + if (prefix != null) { + sb.append("(").append(prefix).append(")"); + } + if (value != null) { + sb.append(" - ").append(value); + } + if (predicates != null && !predicates.isEmpty()) { + for (Predicate predicate : predicates) { + sb.append("["); + predicate.toString(); + sb.append("]"); + } + } + return sb.toString(); + } + + } + + public static final class Predicate { + + private final IdentityValue name; + private final String value; + + public Predicate(final IdentityValue name, final String value) { + super(); + this.name = name; + this.value = value; + } + + public IdentityValue getName() { + return name; + } + + public String getValue() { + return value; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + if (name != null) { + sb.append(name.toString()); + } + if (value != null) { + sb.append("=").append(value); + } + return sb.toString(); + } + + public boolean isLeafList() { + return name == null ? true : false; + } + + } +} diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/IdentityrefCodecImpl.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/IdentityrefCodecImpl.java new file mode 100644 index 0000000000..a8c8b8d6f5 --- /dev/null +++ b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/IdentityrefCodecImpl.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2014 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.helpers; + +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.codec.IdentityrefCodec; +import org.opendaylight.yangtools.yang.data.codec.gson.helpers.IdentityValuesDTO.IdentityValue; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class IdentityrefCodecImpl extends AbstractCodecImpl implements IdentityrefCodec { + private static final Logger LOG = LoggerFactory.getLogger(IdentityrefCodecImpl.class); + + IdentityrefCodecImpl(final SchemaContextUtils schema) { + super(schema); + } + + @Override + public IdentityValuesDTO serialize(final QName data) { + return new IdentityValuesDTO(data.getNamespace().toString(), data.getLocalName(), data.getPrefix(), null); + } + + @Override + public QName deserialize(final IdentityValuesDTO data) { + IdentityValue valueWithNamespace = data.getValuesWithNamespaces().get(0); + Module module = getModuleByNamespace(valueWithNamespace.getNamespace()); + if (module == null) { + LOG.info("Module was not found for namespace {}", valueWithNamespace.getNamespace()); + LOG.info("Idenetityref will be translated as NULL for data - {}", String.valueOf(valueWithNamespace)); + return null; + } + + return QName.create(module.getNamespace(), module.getRevision(), valueWithNamespace.getValue()); + } + +} \ No newline at end of file diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/InstanceIdentifierCodecImpl.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/InstanceIdentifierCodecImpl.java new file mode 100644 index 0000000000..5904859f13 --- /dev/null +++ b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/InstanceIdentifierCodecImpl.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2014 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.helpers; + +import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.data.api.codec.InstanceIdentifierCodec; +import org.opendaylight.yangtools.yang.data.codec.gson.helpers.IdentityValuesDTO.IdentityValue; +import org.opendaylight.yangtools.yang.data.codec.gson.helpers.IdentityValuesDTO.Predicate; +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.ListSchemaNode; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class InstanceIdentifierCodecImpl extends AbstractCodecImpl implements InstanceIdentifierCodec { + private static final Logger LOG = LoggerFactory.getLogger(InstanceIdentifierCodecImpl.class); + + InstanceIdentifierCodecImpl(final SchemaContextUtils schema) { + super(schema); + } + + @Override + public IdentityValuesDTO serialize(final YangInstanceIdentifier data) { + IdentityValuesDTO identityValuesDTO = new IdentityValuesDTO(); + for (PathArgument pathArgument : data.getPathArguments()) { + IdentityValue identityValue = qNameToIdentityValue(pathArgument.getNodeType()); + if (pathArgument instanceof NodeIdentifierWithPredicates && identityValue != null) { + List predicates = keyValuesToPredicateList(((NodeIdentifierWithPredicates) pathArgument) + .getKeyValues()); + identityValue.setPredicates(predicates); + } else if (pathArgument instanceof NodeWithValue && identityValue != null) { + List predicates = new ArrayList<>(); + String value = String.valueOf(((NodeWithValue) pathArgument).getValue()); + predicates.add(new Predicate(null, value)); + identityValue.setPredicates(predicates); + } + identityValuesDTO.add(identityValue); + } + return identityValuesDTO; + } + + @Override + public YangInstanceIdentifier deserialize(final IdentityValuesDTO data) { + List result = new ArrayList(); + IdentityValue valueWithNamespace = data.getValuesWithNamespaces().get(0); + Module module = getModuleByNamespace(valueWithNamespace.getNamespace()); + if (module == null) { + LOG.info("Module by namespace '{}' of first node in instance-identiefier was not found.", + valueWithNamespace.getNamespace()); + LOG.info("Instance-identifier will be translated as NULL for data - {}", + String.valueOf(valueWithNamespace.getValue())); + return null; + } + + DataNodeContainer parentContainer = module; + List identities = data.getValuesWithNamespaces(); + for (int i = 0; i < identities.size(); i++) { + IdentityValue identityValue = identities.get(i); + URI validNamespace = resolveValidNamespace(identityValue.getNamespace()); + DataSchemaNode node = getSchema().findInstanceDataChildByNameAndNamespace( + parentContainer, identityValue.getValue(), validNamespace); + if (node == null) { + LOG.info("'{}' node was not found in {}", identityValue, parentContainer.getChildNodes()); + LOG.info("Instance-identifier will be translated as NULL for data - {}", + String.valueOf(identityValue.getValue())); + return null; + } + QName qName = node.getQName(); + PathArgument pathArgument = null; + if (identityValue.getPredicates().isEmpty()) { + pathArgument = new NodeIdentifier(qName); + } else { + if (node instanceof LeafListSchemaNode) { // predicate is value of leaf-list entry + Predicate leafListPredicate = identityValue.getPredicates().get(0); + if (!leafListPredicate.isLeafList()) { + LOG.info("Predicate's data is not type of leaf-list. It should be in format \".='value'\""); + LOG.info("Instance-identifier will be translated as NULL for data - {}", + String.valueOf(identityValue.getValue())); + return null; + } + pathArgument = new NodeWithValue(qName, leafListPredicate.getValue()); + } else if (node instanceof ListSchemaNode) { // predicates are keys of list + DataNodeContainer listNode = (DataNodeContainer) node; + Map predicatesMap = new HashMap<>(); + for (Predicate predicate : identityValue.getPredicates()) { + validNamespace = resolveValidNamespace(predicate.getName().getNamespace()); + DataSchemaNode listKey = getSchema() + .findInstanceDataChildByNameAndNamespace(listNode, predicate.getName().getValue(), + validNamespace); + predicatesMap.put(listKey.getQName(), predicate.getValue()); + } + pathArgument = new NodeIdentifierWithPredicates(qName, predicatesMap); + } else { + LOG.info("Node {} is not List or Leaf-list.", node); + LOG.info("Instance-identifier will be translated as NULL for data - {}", + String.valueOf(identityValue.getValue())); + return null; + } + } + result.add(pathArgument); + if (i < identities.size() - 1) { // last element in instance-identifier can be other than + // DataNodeContainer + if (node instanceof DataNodeContainer) { + parentContainer = (DataNodeContainer) node; + } else { + LOG.info("Node {} isn't instance of DataNodeContainer", node); + LOG.info("Instance-identifier will be translated as NULL for data - {}", + String.valueOf(identityValue.getValue())); + return null; + } + } + } + + return result.isEmpty() ? null : YangInstanceIdentifier.create(result); + } + + private static List keyValuesToPredicateList(final Map keyValues) { + List result = new ArrayList<>(); + for (QName qName : keyValues.keySet()) { + Object value = keyValues.get(qName); + result.add(new Predicate(qNameToIdentityValue(qName), String.valueOf(value))); + } + return result; + } + + private static IdentityValue qNameToIdentityValue(final QName qName) { + if (qName != null) { + // FIXME: the prefix here is completely arbitrary + return new IdentityValue(qName.getNamespace().toString(), qName.getLocalName(), qName.getPrefix()); + } + return null; + } +} \ No newline at end of file diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/LeafrefCodecImpl.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/LeafrefCodecImpl.java new file mode 100644 index 0000000000..c6d8baf9fe --- /dev/null +++ b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/LeafrefCodecImpl.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2014 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.helpers; + +import org.opendaylight.yangtools.yang.data.api.codec.LeafrefCodec; + +class LeafrefCodecImpl implements LeafrefCodec { + + @Override + public String serialize(final Object data) { + return String.valueOf(data); + } + + @Override + public Object deserialize(final String data) { + return data; + } + +} \ No newline at end of file diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/ObjectCodec.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/ObjectCodec.java new file mode 100644 index 0000000000..abe7cd2e63 --- /dev/null +++ b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/ObjectCodec.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2014 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.helpers; + +import org.opendaylight.yangtools.concepts.Codec; +import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec; +import org.opendaylight.yangtools.yang.model.api.TypeDefinition; +import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition; +import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition; +import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@SuppressWarnings("rawtypes") +final class ObjectCodec extends AbstractCodecImpl implements Codec { + public static final Codec LEAFREF_DEFAULT_CODEC = new LeafrefCodecImpl(); + private static final Logger LOG = LoggerFactory.getLogger(RestCodecFactory.class); + private final Codec instanceIdentifier; + private final Codec identityrefCodec; + private final TypeDefinition type; + + ObjectCodec(final SchemaContextUtils schema, final TypeDefinition typeDefinition) { + super(schema); + type = RestUtil.resolveBaseTypeFrom(typeDefinition); + if (type instanceof IdentityrefTypeDefinition) { + identityrefCodec = new IdentityrefCodecImpl(schema); + } else { + identityrefCodec = null; + } + if (type instanceof InstanceIdentifierTypeDefinition) { + instanceIdentifier = new InstanceIdentifierCodecImpl(schema); + } else { + instanceIdentifier = null; + } + } + + @SuppressWarnings("unchecked") + @Override + public Object deserialize(final Object input) { + try { + if (type instanceof IdentityrefTypeDefinition) { + if (input instanceof IdentityValuesDTO) { + return identityrefCodec.deserialize(input); + } + LOG.debug("Value is not instance of IdentityrefTypeDefinition but is {}. Therefore NULL is used as translation of - {}", + input == null ? "null" : input.getClass(), String.valueOf(input)); + return null; + } else if (type instanceof LeafrefTypeDefinition) { + if (input instanceof IdentityValuesDTO) { + return LEAFREF_DEFAULT_CODEC.deserialize(((IdentityValuesDTO) input).getOriginValue()); + } + return LEAFREF_DEFAULT_CODEC.deserialize(input); + } else if (type instanceof InstanceIdentifierTypeDefinition) { + if (input instanceof IdentityValuesDTO) { + return instanceIdentifier.deserialize(input); + } + LOG.info( + "Value is not instance of InstanceIdentifierTypeDefinition but is {}. Therefore NULL is used as translation of - {}", + input == null ? "null" : input.getClass(), String.valueOf(input)); + return null; + } else { + TypeDefinitionAwareCodec> typeAwarecodec = TypeDefinitionAwareCodec + .from(type); + if (typeAwarecodec != null) { + if (input instanceof IdentityValuesDTO) { + return typeAwarecodec.deserialize(((IdentityValuesDTO) input).getOriginValue()); + } + return typeAwarecodec.deserialize(String.valueOf(input)); + } else { + LOG.debug("Codec for type \"" + type.getQName().getLocalName() + + "\" is not implemented yet."); + return null; + } + } + } catch (ClassCastException e) { + // TODO remove this catch when everyone use codecs + LOG.error("ClassCastException was thrown when codec is invoked with parameter " + String.valueOf(input), + e); + return null; + } + } + + @SuppressWarnings("unchecked") + @Override + public Object serialize(final Object input) { + try { + if (type instanceof IdentityrefTypeDefinition) { + return identityrefCodec.serialize(input); + } else if (type instanceof LeafrefTypeDefinition) { + return LEAFREF_DEFAULT_CODEC.serialize(input); + } else if (type instanceof InstanceIdentifierTypeDefinition) { + return instanceIdentifier.serialize(input); + } else { + TypeDefinitionAwareCodec> typeAwarecodec = TypeDefinitionAwareCodec + .from(type); + if (typeAwarecodec != null) { + return typeAwarecodec.serialize(input); + } else { + LOG.debug("Codec for type \"" + type.getQName().getLocalName() + + "\" is not implemented yet."); + return null; + } + } + } catch (ClassCastException e) { // TODO remove this catch when everyone use codecs + LOG.error( + "ClassCastException was thrown when codec is invoked with parameter " + String.valueOf(input), + e); + return input; + } + } + +} \ No newline at end of file diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/RestCodecFactory.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/RestCodecFactory.java new file mode 100644 index 0000000000..94bba92243 --- /dev/null +++ b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/RestCodecFactory.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2014 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.helpers; + +import com.google.common.annotations.Beta; +import com.google.common.base.Preconditions; + +import org.opendaylight.yangtools.concepts.Codec; +import org.opendaylight.yangtools.yang.model.api.TypeDefinition; + +/** + * This class is implementation-internal and subject to change. Please do not use it. + */ +@Beta +public final class RestCodecFactory { + private final SchemaContextUtils utils; + + private RestCodecFactory(final SchemaContextUtils utils) { + this.utils = Preconditions.checkNotNull(utils); + } + + public static RestCodecFactory create(final SchemaContextUtils utils) { + return new RestCodecFactory(utils); + } + + public final Codec codecFor(final TypeDefinition typeDefinition) { + // FIXME: implement loadingcache + return new ObjectCodec(utils, typeDefinition); + } +} diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/RestUtil.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/RestUtil.java new file mode 100644 index 0000000000..c3d002c4bb --- /dev/null +++ b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/RestUtil.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2014 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.helpers; + + +import com.google.common.annotations.Beta; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.xml.stream.events.StartElement; + +import org.opendaylight.yangtools.yang.data.codec.gson.helpers.IdentityValuesDTO.IdentityValue; +import org.opendaylight.yangtools.yang.data.codec.gson.helpers.IdentityValuesDTO.Predicate; +import org.opendaylight.yangtools.yang.model.api.TypeDefinition; + +/** + * This class is implementation-internal and subject to change. Please do not use it. + */ +@Beta +public final class RestUtil { + + // FIXME: BUG-1275: this is code duplicates data.impl.codec + + public static final String SQUOTE = "'"; + public static final String DQUOTE = "\""; + private static final Pattern PREDICATE_PATTERN = Pattern.compile("\\[(.*?)\\]"); + + public final static TypeDefinition resolveBaseTypeFrom(final TypeDefinition type) { + TypeDefinition superType = type; + while (superType.getBaseType() != null) { + superType = superType.getBaseType(); + } + return superType; + } + + public static IdentityValuesDTO asInstanceIdentifier(final String value, final PrefixesMaping prefixMap) { + String valueTrimmed = value.trim(); + if (!valueTrimmed.startsWith("/")) { + return null; + } + String[] xPathParts = valueTrimmed.split("/"); + if (xPathParts.length < 2) { // must be at least "/pr:node" + return null; + } + IdentityValuesDTO identityValuesDTO = new IdentityValuesDTO(value); + for (int i = 1; i < xPathParts.length; i++) { + String xPathPartTrimmed = xPathParts[i].trim(); + + String xPathPartStr = getIdAndPrefixAsStr(xPathPartTrimmed); + IdentityValue identityValue = toIdentity(xPathPartStr, prefixMap); + if (identityValue == null) { + return null; + } + + List predicates = toPredicates(xPathPartTrimmed, prefixMap); + if (predicates == null) { + return null; + } + identityValue.setPredicates(predicates); + + identityValuesDTO.add(identityValue); + } + return identityValuesDTO.getValuesWithNamespaces().isEmpty() ? null : identityValuesDTO; + } + + private static String getIdAndPrefixAsStr(final String pathPart) { + int predicateStartIndex = pathPart.indexOf("["); + return predicateStartIndex == -1 ? pathPart : pathPart.substring(0, predicateStartIndex); + } + + private static IdentityValue toIdentity(final String xPathPart, final PrefixesMaping prefixMap) { + String xPathPartTrimmed = xPathPart.trim(); + if (xPathPartTrimmed.isEmpty()) { + return null; + } + String[] prefixAndIdentifier = xPathPartTrimmed.split(":"); + // it is not "prefix:value" + if (prefixAndIdentifier.length != 2) { + return null; + } + String prefix = prefixAndIdentifier[0].trim(); + String identifier = prefixAndIdentifier[1].trim(); + if (prefix.isEmpty() || identifier.isEmpty()) { + return null; + } + String namespace = prefixMap.getNamespace(prefix); + return new IdentityValue(namespace, identifier, namespace.equals(prefix) ? null : prefix); + } + + private static List toPredicates(final String predicatesStr, final PrefixesMaping prefixMap) { + List result = new ArrayList<>(); + List predicates = new ArrayList<>(); + Matcher matcher = PREDICATE_PATTERN.matcher(predicatesStr); + while (matcher.find()) { + predicates.add(matcher.group(1).trim()); + } + for (String predicate : predicates) { + int indexOfEqualityMark = predicate.indexOf("="); + if (indexOfEqualityMark != -1) { + String predicateValue = toPredicateValue(predicate.substring(indexOfEqualityMark + 1)); + if (predicate.startsWith(".")) { // it is leaf-list + if (predicateValue == null) { + return null; + } + result.add(new Predicate(null, predicateValue)); + } else { + IdentityValue identityValue = toIdentity(predicate.substring(0, indexOfEqualityMark), prefixMap); + if (identityValue == null || predicateValue == null) { + return null; + } + result.add(new Predicate(identityValue, predicateValue)); + } + } + } + return result; + } + + private static String toPredicateValue(final String predicatedValue) { + String predicatedValueTrimmed = predicatedValue.trim(); + if ((predicatedValueTrimmed.startsWith(DQUOTE) || predicatedValueTrimmed.startsWith(SQUOTE)) + && (predicatedValueTrimmed.endsWith(DQUOTE) || predicatedValueTrimmed.endsWith(SQUOTE))) { + return predicatedValueTrimmed.substring(1, predicatedValueTrimmed.length() - 1); + } + return null; + } + + public interface PrefixesMaping { + public String getNamespace(String prefix); + } + + public static class PrefixMapingFromXml implements PrefixesMaping { + StartElement startElement = null; + + public PrefixMapingFromXml(final StartElement startElement) { + this.startElement = startElement; + } + + @Override + public String getNamespace(final String prefix) { + return startElement.getNamespaceContext().getNamespaceURI(prefix); + } + } + + public static class PrefixMapingFromJson implements PrefixesMaping { + + @Override + public String getNamespace(final String prefix) { + return prefix; + } + } + +} diff --git a/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/SchemaContextUtils.java b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/SchemaContextUtils.java new file mode 100644 index 0000000000..5e8f6f1e1e --- /dev/null +++ b/yang/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/helpers/SchemaContextUtils.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2014 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.helpers; + +import com.google.common.annotations.Beta; +import com.google.common.base.Function; +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +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.ContainerSchemaNode; +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.ListSchemaNode; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; + +/** + * This class is implementation-internal and subject to change. Please do not use it. + */ +@Beta +public final class SchemaContextUtils { + private final SchemaContext schemaContext; + + private SchemaContextUtils(final SchemaContext schemaContext) { + this.schemaContext = Preconditions.checkNotNull(schemaContext); + } + + public static SchemaContextUtils create(final SchemaContext schemaContext) { + return new SchemaContextUtils(schemaContext); + } + + public URI findNamespaceByModuleName(final String moduleName) { + final Module module = this.findModuleByName(moduleName); + return module == null ? null : module.getNamespace(); + } + + + public Module findModuleByName(final String moduleName) { + checkPreconditions(); + Preconditions.checkArgument(moduleName != null && !moduleName.isEmpty()); + return schemaContext.findModuleByName(moduleName, null); + } + + public Module findModuleByNamespace(final URI namespace) { + this.checkPreconditions(); + Preconditions.checkArgument(namespace != null); + return schemaContext.findModuleByNamespaceAndRevision(namespace, null); + } + + private void checkPreconditions() { + if (schemaContext == null) { + throw new IllegalStateException("Schema context isn't set."); + } + } + + public DataSchemaNode findInstanceDataChildByNameAndNamespace(final DataNodeContainer container, final String name, + final URI namespace) { + Preconditions. checkNotNull(namespace); + + final List potentialSchemaNodes = findInstanceDataChildrenByName(container, name); + + Predicate filter = new Predicate() { + @Override + public boolean apply(final DataSchemaNode node) { + return Objects.equal(node.getQName().getNamespace(), namespace); + } + }; + + Iterable result = Iterables.filter(potentialSchemaNodes, filter); + return Iterables.getFirst(result, null); + } + + public List findInstanceDataChildrenByName(final DataNodeContainer container, final String name) { + Preconditions. checkNotNull(container); + Preconditions. checkNotNull(name); + + List instantiatedDataNodeContainers = new ArrayList(); + collectInstanceDataNodeContainers(instantiatedDataNodeContainers, container, name); + return instantiatedDataNodeContainers; + } + + private void collectInstanceDataNodeContainers(final List potentialSchemaNodes, + final DataNodeContainer container, final String name) { + + Predicate filter = new Predicate() { + @Override + public boolean apply(final DataSchemaNode node) { + return Objects.equal(node.getQName().getLocalName(), name); + } + }; + + Iterable nodes = Iterables.filter(container.getChildNodes(), filter); + + // Can't combine this loop with the filter above because the filter is + // lazily-applied by Iterables.filter. + for (final DataSchemaNode potentialNode : nodes) { + if (isInstantiatedDataSchema(potentialNode)) { + potentialSchemaNodes.add(potentialNode); + } + } + + Iterable choiceNodes = Iterables.filter(container.getChildNodes(), ChoiceNode.class); + Iterable> map = Iterables.transform(choiceNodes, CHOICE_FUNCTION); + + final Iterable allCases = Iterables. concat(map); + for (final ChoiceCaseNode caze : allCases) { + collectInstanceDataNodeContainers(potentialSchemaNodes, caze, name); + } + } + + public boolean isInstantiatedDataSchema(final DataSchemaNode node) { + return node instanceof LeafSchemaNode || node instanceof LeafListSchemaNode + || node instanceof ContainerSchemaNode || node instanceof ListSchemaNode + || node instanceof AnyXmlSchemaNode; + } + + private final Function> CHOICE_FUNCTION = new Function>() { + @Override + public Set apply(final ChoiceNode node) { + return node.getCases(); + } + }; + +} diff --git a/yang/yang-data-codec-gson/src/test/java/org/opendaylight/yangtools/yang/data/codec/gson/StreamToNormalizedNodeTest.java b/yang/yang-data-codec-gson/src/test/java/org/opendaylight/yangtools/yang/data/codec/gson/StreamToNormalizedNodeTest.java new file mode 100644 index 0000000000..1010989bc8 --- /dev/null +++ b/yang/yang-data-codec-gson/src/test/java/org/opendaylight/yangtools/yang/data/codec/gson/StreamToNormalizedNodeTest.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2014 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 com.google.gson.stream.JsonReader; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.stream.LoggingNormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter; +import org.opendaylight.yangtools.yang.data.impl.schema.Builders; +import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeContainerBuilder; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.parser.api.YangContextParser; +import org.opendaylight.yangtools.yang.parser.impl.YangParserImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class StreamToNormalizedNodeTest { + private static final Logger LOG = LoggerFactory.getLogger(StreamToNormalizedNodeTest.class); + private static SchemaContext schemaContext; + private static String streamAsString; + + @BeforeClass + public static void initialization() throws IOException { + schemaContext = loadModules("/complexjson/yang"); + streamAsString = loadTextFile(StreamToNormalizedNodeTest.class.getResource("/complexjson/complex-json.json") + .getPath()); + } + + /** + * Demonstrates how to log events produced by a {@link JsonReader}. + * + * @throws IOException + */ + @Test + public void ownStreamWriterImplementationDemonstration() throws IOException { + // GSON's JsonReader reading from the loaded string (our event source) + final JsonReader reader = new JsonReader(new StringReader(streamAsString)); + + // StreamWriter which outputs SLF4J events + final LoggingNormalizedNodeStreamWriter logWriter = new LoggingNormalizedNodeStreamWriter(); + + // JSON -> StreamWriter parser + try (final JsonParserStream jsonHandler = JsonParserStream.create(logWriter, schemaContext)) { + // Process multiple readers, flush()/close() as needed + jsonHandler.parse(reader); + } + } + + /** + * Demonstrates how to create an immutable NormalizedNode tree from a {@link JsonReader} and + * then writes the data back into string representation. + * + * @throws IOException + */ + @Test + public void immutableNormalizedNodeStreamWriterDemonstration() throws IOException { + /* + * This is the parsing part + */ + // This is where we will output the nodes + final NormalizedNodeContainerBuilder> parent = + Builders.containerBuilder().withNodeIdentifier(new NodeIdentifier(QName.create("dummy", "2014-12-31", "dummy"))); + + // StreamWriter which attaches NormalizedNode under parent + final NormalizedNodeStreamWriter streamWriter = ImmutableNormalizedNodeStreamWriter.from(parent); + + // JSON -> StreamWriter parser + try (JsonParserStream handler = JsonParserStream.create(streamWriter, schemaContext)) { + handler.parse(new JsonReader(new StringReader(streamAsString))); + } + + // Finally build the node + final NormalizedNode parsedData = parent.build(); + LOG.debug("Parsed NormalizedNodes: {}", parsedData); + + /* + * This is the serialization part. + */ + // We want to write the first child out + final DataContainerChild firstChild = ((ContainerNode) parsedData).getValue().iterator().next(); + LOG.debug("Serializing first child: {}", firstChild); + + // String holder + final StringWriter writer = new StringWriter(); + + // StreamWriter which outputs JSON strings + final NormalizedNodeStreamWriter jsonStream = JSONNormalizedNodeStreamWriter.create(schemaContext, writer, 2); + + // NormalizedNode -> StreamWriter + final NormalizedNodeWriter nodeWriter = NormalizedNodeWriter.forStreamWriter(jsonStream); + + // Write multiple NormalizedNodes fluently, flush()/close() as needed + nodeWriter.write(firstChild).close(); + + // Just to put it somewhere + LOG.debug("Serialized JSON: {}", writer.toString()); + } + + private static SchemaContext loadModules(final String resourceDirectory) throws IOException { + YangContextParser parser = new YangParserImpl(); + String path = StreamToNormalizedNodeTest.class.getResource(resourceDirectory).getPath(); + final File testDir = new File(path); + final String[] fileList = testDir.list(); + final List testFiles = new ArrayList(); + if (fileList == null) { + throw new FileNotFoundException(resourceDirectory); + } + for (String fileName : fileList) { + if (new File(testDir, fileName).isDirectory() == false) { + testFiles.add(new File(testDir, fileName)); + } + } + return parser.parseFiles(testFiles); + } + + private static String loadTextFile(final String filePath) throws IOException { + FileReader fileReader = new FileReader(filePath); + BufferedReader bufReader = new BufferedReader(fileReader); + + String line = null; + StringBuilder result = new StringBuilder(); + while ((line = bufReader.readLine()) != null) { + result.append(line); + } + bufReader.close(); + return result.toString(); + } +} diff --git a/yang/yang-data-codec-gson/src/test/resources/complexjson/complex-json.json b/yang/yang-data-codec-gson/src/test/resources/complexjson/complex-json.json new file mode 100644 index 0000000000..8ecaa37e4f --- /dev/null +++ b/yang/yang-data-codec-gson/src/test/resources/complexjson/complex-json.json @@ -0,0 +1,41 @@ +{ + "complexjson:cont1": { + "lf12-any":[ + { + "anyxml-in-data":"foo" + } + ], + + "lf13-any":{ + "anyxml-in-data":"foo" + }, + + "lf14-any":"anyxml data", + + "lflst11":["lflst11 value1","lflst11 value2"], + + "lst11":[ + { + "key111":"key111 value", + "lf112":"/complexjson:cont1/complexjson:lflst11", + "lf113":"lf113 value", + "lf111":"lf111 value" + } + ], + "lf11" : "453", + "lf12_1" : "lf12 value", + "lf13" : "lf13 value", + "complexjson-augmentation:lf15_11" : "lf15_11 value from augmentation", + "complexjson-augmentation:lf15_12" : "lf15_12 value from augmentation", + "lf15_11" : "one two", + "lf15_12" : "complexjson:lf11", + "lf15_21" : "lf15_21 value", + "lf17" : "lf17 value", + + "lst12":[ + { + "lf121":"lf121 value" + } + ] + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..d4ea623b19 --- /dev/null +++ b/yang/yang-data-codec-gson/src/test/resources/complexjson/yang/complexjson-augmentation.yang @@ -0,0 +1,37 @@ +module complexjson-augmentation { + namespace "ns:complex:json:augmentation"; + prefix cjaug; + + import complexjson { + prefix cj; + } + + revision "2014-08-14" { + } + + augment "/cj:cont1/cj:choc11/cj:c11A" { + leaf lf15_11 { + type string; + } + leaf lf15_12 { + type string; + } + + } + + augment "/cj:cont1" { + leaf lf12_1aug { + type string; + } + leaf lf12_2aug { + type string; + } + } + + augment "/cj:cont1/cj:choc11/cj:c11A" { + leaf lf15_21aug { + type string; + } + } + +} diff --git a/yang/yang-data-codec-gson/src/test/resources/complexjson/yang/complexjson.yang b/yang/yang-data-codec-gson/src/test/resources/complexjson/yang/complexjson.yang new file mode 100644 index 0000000000..6c07d656da --- /dev/null +++ b/yang/yang-data-codec-gson/src/test/resources/complexjson/yang/complexjson.yang @@ -0,0 +1,140 @@ +module complexjson { + namespace "ns:complex:json"; + prefix cj; + + revision "2014-08-11" { + } + + + identity ident; + + container cont1 { + + anyxml lf12-any; + anyxml lf13-any; + anyxml lf14-any; + + leaf lf11 { + type int32; + } + + leaf-list lflst11 { + type string; + } + + list lst11 { + key "key111 lf111"; + leaf key111 { + type string; + } + leaf lf111 { + type string; + } + leaf lf112 { + type instance-identifier; + } + leaf lf113 { + type string; + } + } + + list lst12 { + leaf lf121 { + type string; + } + leaf lf122 { + type string; + } + } + + + choice choc11 { + case c11A { + leaf lf13 { + type string; + } + } + leaf lf16 { + type string; + } + } + + choice choc12 { + case c12A { + } + } + } + + + augment "/cont1/choc12" { + case c12B { + leaf lf17 { + type string; + } + } + } + + + augment "/cont1" { + container cont11 { + leaf lf111 { + type string; + } + } + } + + augment "/cont1" { + leaf lf12_1 { + type string; + } + leaf lf12_2 { + type string; + } + } + + augment "/cont1" { + leaf lf12_3 { + type string; + } + } + + + augment "/cont1/choc11" { + case c11B { + leaf lf14_1 { + type string; + } + } + } + + augment "/cont1/choc11" { + case c11C { + leaf lf14_2 { + type string; + } + } + } + + augment "/cont1/choc11/c11A" { + leaf lf15_11 { + type bits { + bit one; + bit two; + bit three; + } + } + leaf lf15_12 { + type identityref { + base ident; + } + } + + } + + augment "/cont1/choc11/c11A" { + leaf lf15_21 { + type string; + } + } + +} -- 2.36.6