From: Jan Hajnar Date: Tue, 2 Jun 2015 15:47:08 +0000 (+0200) Subject: BUG 2155 - depth parameter in URI X-Git-Tag: release/lithium~39 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=commitdiff_plain;h=f94de91bba1210fa852aaab0490743f9f3553e99 BUG 2155 - depth parameter in URI * added interface RestconfNormalizedNodeWriter * added two implementations of RestconfNormalizedNodeWriter, RestconfDelegatingNormalizedNodeWriter just calls normalized node writer from yangtools and DepthAwareNormalizedNodeWriter is writer implementation tha checks depth * added CutDataToCorrectDepthTest (randomly failing, needs to be checked or removed for now) * added condition to xml and json normalized node writers to create depth aware normalized node writer id depth is specified in writer parameters Change-Id: I922942e24cbe505c2803644c25acd755fe4dfae7 Signed-off-by: Jozef Gloncak Signed-off-by: Jan Hajnar --- diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/api/RestconfNormalizedNodeWriter.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/api/RestconfNormalizedNodeWriter.java new file mode 100644 index 0000000000..c39bf3a9b8 --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/api/RestconfNormalizedNodeWriter.java @@ -0,0 +1,11 @@ +package org.opendaylight.controller.sal.rest.api; + +import java.io.Closeable; +import java.io.Flushable; +import java.io.IOException; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; + +public interface RestconfNormalizedNodeWriter extends Flushable, Closeable { + + RestconfNormalizedNodeWriter write(final NormalizedNode node) throws IOException; +} diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/DepthAwareNormalizedNodeWriter.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/DepthAwareNormalizedNodeWriter.java new file mode 100644 index 0000000000..c439ee66e3 --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/DepthAwareNormalizedNodeWriter.java @@ -0,0 +1,320 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.sal.rest.impl; + +import static org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter.UNKNOWN_SIZE; +import com.google.common.annotations.Beta; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import java.io.IOException; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import org.opendaylight.controller.sal.rest.api.RestconfNormalizedNodeWriter; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; +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; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamAttributeWriter; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This is an experimental iterator over a {@link NormalizedNode}. This is essentially + * the opposite of a {@link XMLStreamReader} -- unlike instantiating an iterator over + * the backing data, this encapsulates a {@link NormalizedNodeStreamWriter} and allows + * us to write multiple nodes. + */ +@Beta +public class DepthAwareNormalizedNodeWriter implements RestconfNormalizedNodeWriter { + private final NormalizedNodeStreamWriter writer; + protected int currentDepth = 0; + protected final int maxDepth; + + private DepthAwareNormalizedNodeWriter(final NormalizedNodeStreamWriter writer, final int maxDepth) { + this.writer = Preconditions.checkNotNull(writer); + this.maxDepth = maxDepth; + } + + protected final NormalizedNodeStreamWriter getWriter() { + return writer; + } + + /** + * Create a new writer backed by a {@link NormalizedNodeStreamWriter}. + * + * @param writer Back-end writer + * @return A new instance. + */ + public static DepthAwareNormalizedNodeWriter forStreamWriter(final NormalizedNodeStreamWriter writer, final int maxDepth) { + return forStreamWriter(writer, true, maxDepth); + } + + /** + * Create a new writer backed by a {@link NormalizedNodeStreamWriter}. Unlike the simple {@link #forStreamWriter(NormalizedNodeStreamWriter)} + * method, this allows the caller to switch off RFC6020 XML compliance, providing better + * throughput. The reason is that the XML mapping rules in RFC6020 require the encoding + * to emit leaf nodes which participate in a list's key first and in the order in which + * they are defined in the key. For JSON, this requirement is completely relaxed and leaves + * can be ordered in any way we see fit. The former requires a bit of work: first a lookup + * for each key and then for each emitted node we need to check whether it was already + * emitted. + * + * @param writer Back-end writer + * @param orderKeyLeaves whether the returned instance should be RFC6020 XML compliant. + * @return A new instance. + */ + public static DepthAwareNormalizedNodeWriter forStreamWriter(final NormalizedNodeStreamWriter writer, final boolean + orderKeyLeaves, final int maxDepth) { + if (orderKeyLeaves) { + return new OrderedDepthAwareNormalizedNodeWriter(writer, maxDepth); + } else { + return new DepthAwareNormalizedNodeWriter(writer, maxDepth); + } + } + + /** + * Iterate over the provided {@link NormalizedNode} and emit write + * events to the encapsulated {@link NormalizedNodeStreamWriter}. + * + * @param node Node + * @return + * @throws IOException when thrown from the backing writer. + */ + public final DepthAwareNormalizedNodeWriter 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); + } + + @Override + public void flush() throws IOException { + writer.flush(); + } + + @Override + public void close() throws IOException { + writer.flush(); + writer.close(); + } + + /** + * Emit a best guess of a hint for a particular set of children. It evaluates the + * iterable to see if the size can be easily gotten to. If it is, we hint at the + * real number of child nodes. Otherwise we emit UNKNOWN_SIZE. + * + * @param children Child nodes + * @return Best estimate of the collection size required to hold all the children. + */ + static final int childSizeHint(final Iterable children) { + return (children instanceof Collection) ? ((Collection) children).size() : UNKNOWN_SIZE; + } + + private boolean wasProcessAsSimpleNode(final NormalizedNode node) throws IOException { + if (node instanceof LeafSetEntryNode) { + if (currentDepth < maxDepth) { + final LeafSetEntryNode nodeAsLeafList = (LeafSetEntryNode) node; + if (writer instanceof NormalizedNodeStreamAttributeWriter) { + ((NormalizedNodeStreamAttributeWriter) writer).leafSetEntryNode(nodeAsLeafList.getValue(), nodeAsLeafList.getAttributes()); + } else { + writer.leafSetEntryNode(nodeAsLeafList.getValue()); + } + } + return true; + } else if (node instanceof LeafNode) { + final LeafNode nodeAsLeaf = (LeafNode)node; + if(writer instanceof NormalizedNodeStreamAttributeWriter) { + ((NormalizedNodeStreamAttributeWriter) writer).leafNode(nodeAsLeaf.getIdentifier(), nodeAsLeaf.getValue(), nodeAsLeaf.getAttributes()); + } else { + 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; + } + + /** + * Emit events for all children and then emit an endNode() event. + * + * @param children Child iterable + * @return True + * @throws IOException when the writer reports it + */ + protected final boolean writeChildren(final Iterable> children) throws IOException { + if (currentDepth < maxDepth) { + for (NormalizedNode child : children) { + write(child); + } + } + writer.endNode(); + return true; + } + + protected boolean writeMapEntryChildren(final MapEntryNode mapEntryNode) throws IOException { + if (currentDepth < maxDepth) { + writeChildren(mapEntryNode.getValue()); + } else if (currentDepth == maxDepth) { + writeOnlyKeys(mapEntryNode.getIdentifier().getKeyValues()); + } + return true; + } + + private void writeOnlyKeys(Map keyValues) throws IllegalArgumentException, IOException { + for (Map.Entry entry : keyValues.entrySet()) { + writer.leafNode(new NodeIdentifier(entry.getKey()), entry.getValue()); + } + writer.endNode(); + + } + + protected boolean writeMapEntryNode(final MapEntryNode node) throws IOException { + if(writer instanceof NormalizedNodeStreamAttributeWriter) { + ((NormalizedNodeStreamAttributeWriter) writer) + .startMapEntryNode(node.getIdentifier(), childSizeHint(node.getValue()), node.getAttributes()); + } else { + writer.startMapEntryNode(node.getIdentifier(), childSizeHint(node.getValue())); + } + currentDepth++; + writeMapEntryChildren(node); + currentDepth--; + return true; + } + + private boolean wasProcessedAsCompositeNode(final NormalizedNode node) throws IOException { + boolean processedAsCompositeNode = false; + if (node instanceof ContainerNode) { + final ContainerNode n = (ContainerNode) node; + if(writer instanceof NormalizedNodeStreamAttributeWriter) { + ((NormalizedNodeStreamAttributeWriter) writer).startContainerNode(n.getIdentifier(), childSizeHint(n.getValue()), n.getAttributes()); + } else { + writer.startContainerNode(n.getIdentifier(), childSizeHint(n.getValue())); + } + currentDepth++; + processedAsCompositeNode = writeChildren(n.getValue()); + currentDepth--; + } + else if (node instanceof MapEntryNode) { + processedAsCompositeNode = writeMapEntryNode((MapEntryNode) node); + } + else if (node instanceof UnkeyedListEntryNode) { + final UnkeyedListEntryNode n = (UnkeyedListEntryNode) node; + writer.startUnkeyedListItem(n.getIdentifier(), childSizeHint(n.getValue())); + currentDepth++; + processedAsCompositeNode = writeChildren(n.getValue()); + currentDepth--; + } + else if (node instanceof ChoiceNode) { + final ChoiceNode n = (ChoiceNode) node; + writer.startChoiceNode(n.getIdentifier(), childSizeHint(n.getValue())); + processedAsCompositeNode = writeChildren(n.getValue()); + } + else if (node instanceof AugmentationNode) { + final AugmentationNode n = (AugmentationNode) node; + writer.startAugmentationNode(n.getIdentifier()); + processedAsCompositeNode = writeChildren(n.getValue()); + } + else if (node instanceof UnkeyedListNode) { + final UnkeyedListNode n = (UnkeyedListNode) node; + writer.startUnkeyedList(n.getIdentifier(), childSizeHint(n.getValue())); + processedAsCompositeNode = writeChildren(n.getValue()); + } + else if (node instanceof OrderedMapNode) { + final OrderedMapNode n = (OrderedMapNode) node; + writer.startOrderedMapNode(n.getIdentifier(), childSizeHint(n.getValue())); + processedAsCompositeNode = writeChildren(n.getValue()); + } + else if (node instanceof MapNode) { + final MapNode n = (MapNode) node; + writer.startMapNode(n.getIdentifier(), childSizeHint(n.getValue())); + processedAsCompositeNode = writeChildren(n.getValue()); + } + else if (node instanceof LeafSetNode) { + //covers also OrderedLeafSetNode for which doesn't exist start* method + final LeafSetNode n = (LeafSetNode) node; + writer.startLeafSet(n.getIdentifier(), childSizeHint(n.getValue())); + currentDepth++; + processedAsCompositeNode = writeChildren(n.getValue()); + currentDepth--; + } + + return processedAsCompositeNode; + } + + private static final class OrderedDepthAwareNormalizedNodeWriter extends DepthAwareNormalizedNodeWriter { + private static final Logger LOG = LoggerFactory.getLogger(OrderedDepthAwareNormalizedNodeWriter.class); + + OrderedDepthAwareNormalizedNodeWriter(final NormalizedNodeStreamWriter writer, final int maxDepth) { + super(writer, maxDepth); + } + + @Override + protected boolean writeMapEntryNode(final MapEntryNode node) throws IOException { + final NormalizedNodeStreamWriter writer = getWriter(); + if(writer instanceof NormalizedNodeStreamAttributeWriter) { + ((NormalizedNodeStreamAttributeWriter) writer).startMapEntryNode(node.getIdentifier(), childSizeHint(node.getValue()), node.getAttributes()); + } else { + writer.startMapEntryNode(node.getIdentifier(), childSizeHint(node.getValue())); + } + + final Set qnames = node.getIdentifier().getKeyValues().keySet(); + // Write out all the key children + for (QName qname : qnames) { + final Optional> child = node.getChild(new NodeIdentifier(qname)); + if (child.isPresent()) { + write(child.get()); + } else { + LOG.info("No child for key element {} found", qname); + } + } + + // Write all the rest + currentDepth++; + boolean result = writeChildren(Iterables.filter(node.getValue(), new Predicate>() { + @Override + public boolean apply(final NormalizedNode input) { + if (input instanceof AugmentationNode) { + return true; + } + if (!qnames.contains(input.getNodeType())) { + return true; + } + + LOG.debug("Skipping key child {}", input); + return false; + } + })); + currentDepth--; + return result; + } + } +} diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/NormalizedNodeJsonBodyWriter.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/NormalizedNodeJsonBodyWriter.java index 2fa37c7745..2cdded2813 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/NormalizedNodeJsonBodyWriter.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/NormalizedNodeJsonBodyWriter.java @@ -8,6 +8,7 @@ package org.opendaylight.controller.sal.rest.impl; import com.google.common.base.Charsets; +import com.google.common.base.Optional; import com.google.gson.stream.JsonWriter; import java.io.IOException; import java.io.OutputStream; @@ -22,6 +23,7 @@ import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.ext.MessageBodyWriter; import javax.ws.rs.ext.Provider; import org.opendaylight.controller.sal.rest.api.Draft02; +import org.opendaylight.controller.sal.rest.api.RestconfNormalizedNodeWriter; import org.opendaylight.controller.sal.rest.api.RestconfService; import org.opendaylight.controller.sal.restconf.impl.InstanceIdentifierContext; import org.opendaylight.controller.sal.restconf.impl.NormalizedNodeContext; @@ -31,7 +33,6 @@ import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; 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.codec.gson.JSONCodecFactory; import org.opendaylight.yangtools.yang.data.codec.gson.JSONNormalizedNodeStreamWriter; import org.opendaylight.yangtools.yang.data.codec.gson.JsonWriterFactory; @@ -74,20 +75,21 @@ public class NormalizedNodeJsonBodyWriter implements MessageBodyWriter context, NormalizedNode data) throws IOException { - final NormalizedNodeWriter nnWriter; + InstanceIdentifierContext context, NormalizedNode data, Optional depth) throws + IOException { + final RestconfNormalizedNodeWriter nnWriter; if (SchemaPath.ROOT.equals(path)) { /* * Creates writer without initialNs and we write children of root data container * which is not visible in restconf */ - nnWriter = createNormalizedNodeWriter(context,path,jsonWriter); + nnWriter = createNormalizedNodeWriter(context,path,jsonWriter, depth); writeChildren(nnWriter,(ContainerNode) data); } else if (context.getSchemaNode() instanceof RpcDefinition) { /* @@ -95,7 +97,7 @@ public class NormalizedNodeJsonBodyWriter implements MessageBodyWriter child : data.getValue()) { nnWriter.write(child); } } - private NormalizedNodeWriter createNormalizedNodeWriter(final InstanceIdentifierContext context, - final SchemaPath path, final JsonWriter jsonWriter) { + private RestconfNormalizedNodeWriter createNormalizedNodeWriter(final InstanceIdentifierContext context, + final SchemaPath path, final JsonWriter jsonWriter, Optional depth) { final SchemaNode schema = context.getSchemaNode(); final JSONCodecFactory codecs = getCodecFactory(context); @@ -135,7 +137,11 @@ public class NormalizedNodeJsonBodyWriter implements MessageBodyWriter pathContext, NormalizedNode data) throws IOException { - final NormalizedNodeWriter nnWriter; + private void writeNormalizedNode(XMLStreamWriter xmlWriter, SchemaPath schemaPath, InstanceIdentifierContext + pathContext, NormalizedNode data, Optional depth) throws IOException { + final RestconfNormalizedNodeWriter nnWriter; final SchemaContext schemaCtx = pathContext.getSchemaContext(); if (SchemaPath.ROOT.equals(schemaPath)) { - nnWriter = createNormalizedNodeWriter(xmlWriter, schemaCtx, schemaPath); + nnWriter = createNormalizedNodeWriter(xmlWriter, schemaCtx, schemaPath, depth); writeElements(xmlWriter, nnWriter, (ContainerNode) data); } else if (pathContext.getSchemaNode() instanceof RpcDefinition) { - nnWriter = createNormalizedNodeWriter(xmlWriter, schemaCtx, ((RpcDefinition) pathContext.getSchemaNode()).getOutput().getPath()); + nnWriter = createNormalizedNodeWriter(xmlWriter, schemaCtx, + ((RpcDefinition) pathContext.getSchemaNode()).getOutput().getPath(), depth); writeElements(xmlWriter, nnWriter, (ContainerNode) data); } else { - nnWriter = createNormalizedNodeWriter(xmlWriter, schemaCtx, schemaPath.getParent()); + nnWriter = createNormalizedNodeWriter(xmlWriter, schemaCtx, schemaPath.getParent(), depth); if (data instanceof MapEntryNode) { // Restconf allows returning one list item. We need to wrap it // in map node in order to serialize it properly @@ -114,13 +118,18 @@ public class NormalizedNodeXmlBodyWriter implements MessageBodyWriter depth) { NormalizedNodeStreamWriter xmlStreamWriter = XMLStreamNormalizedNodeStreamWriter.create(xmlWriter, schemaContext, schemaPath); - return NormalizedNodeWriter.forStreamWriter(xmlStreamWriter); + if (depth.isPresent()) { + return DepthAwareNormalizedNodeWriter.forStreamWriter(xmlStreamWriter, depth.get()); + } else { + return RestconfDelegatingNormalizedNodeWriter.forStreamWriter(xmlStreamWriter); + } } - private void writeElements(final XMLStreamWriter xmlWriter, final NormalizedNodeWriter nnWriter, final ContainerNode data) + private void writeElements(final XMLStreamWriter xmlWriter, final RestconfNormalizedNodeWriter nnWriter, + final ContainerNode data) throws IOException { try { final QName name = data.getNodeType(); diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/RestconfDelegatingNormalizedNodeWriter.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/RestconfDelegatingNormalizedNodeWriter.java new file mode 100644 index 0000000000..d916a7cc05 --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/RestconfDelegatingNormalizedNodeWriter.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.sal.rest.impl; + +import java.io.IOException; +import org.opendaylight.controller.sal.rest.api.RestconfNormalizedNodeWriter; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter; + +/** + * This class just delegates all of the functionality to Yangtools normalized node writer + */ +public class RestconfDelegatingNormalizedNodeWriter implements RestconfNormalizedNodeWriter { + private NormalizedNodeWriter delegNNWriter; + + private RestconfDelegatingNormalizedNodeWriter(NormalizedNodeStreamWriter streamWriter, final boolean + orderKeyLeaves) { + this.delegNNWriter = NormalizedNodeWriter.forStreamWriter(streamWriter, orderKeyLeaves); + } + + public static RestconfDelegatingNormalizedNodeWriter forStreamWriter(final NormalizedNodeStreamWriter writer) { + return forStreamWriter(writer, true); + } + + public static RestconfDelegatingNormalizedNodeWriter forStreamWriter(final NormalizedNodeStreamWriter writer, final boolean + orderKeyLeaves) { + return new RestconfDelegatingNormalizedNodeWriter(writer, orderKeyLeaves); + } + + public RestconfDelegatingNormalizedNodeWriter write(final NormalizedNode node) throws IOException { + delegNNWriter.write(node); + return this; + } + + @Override + public void flush() throws IOException { + delegNNWriter.flush(); + } + + @Override + public void close() throws IOException { + delegNNWriter.close(); + } +} diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/NormalizedNodeContext.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/NormalizedNodeContext.java index d72be873ec..3adecaf8f3 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/NormalizedNodeContext.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/NormalizedNodeContext.java @@ -1,3 +1,10 @@ +/* + * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ package org.opendaylight.controller.sal.restconf.impl; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; @@ -21,7 +28,7 @@ public class NormalizedNodeContext { this.context = context; this.data = data; // default writer parameters - this.writerParameters = new WriterParameters(false, Integer.MAX_VALUE); + this.writerParameters = new WriterParameters.WriterParametersBuilder().build(); } public InstanceIdentifierContext getInstanceIdentifierContext() { diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/QueryParametersParser.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/QueryParametersParser.java index b567d2a1ee..4fc716e78a 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/QueryParametersParser.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/QueryParametersParser.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the @@ -28,20 +28,18 @@ public class QueryParametersParser { } } - public static WriterParameters parseKnownWriterParameters(final UriInfo info) { - boolean prettyPrint; - int depth; + public static WriterParameters parseWriterParameters(final UriInfo info) { + WriterParameters.WriterParametersBuilder wpBuilder = new WriterParameters.WriterParametersBuilder(); String param = info.getQueryParameters(false).getFirst(UriParameters.DEPTH.toString()); - if (Strings.isNullOrEmpty(param) || "unbounded".equals(param)) { - depth = Integer.MAX_VALUE; - } else { + if (!Strings.isNullOrEmpty(param) && !"unbounded".equals(param)) { try { - depth = Integer.valueOf(param); + final int depth = Integer.valueOf(param); if (depth < 1) { throw new RestconfDocumentedException(new RestconfError(RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.INVALID_VALUE, "Invalid depth parameter: " + depth, null, "The depth parameter must be an integer > 1 or \"unbounded\"")); } + wpBuilder.setDepth(depth); } catch (final NumberFormatException e) { throw new RestconfDocumentedException(new RestconfError(RestconfError.ErrorType.PROTOCOL, RestconfError.ErrorTag.INVALID_VALUE, "Invalid depth parameter: " + e.getMessage(), null, @@ -49,8 +47,8 @@ public class QueryParametersParser { } } param = info.getQueryParameters(false).getFirst(UriParameters.PRETTY_PRINT.toString()); - prettyPrint = "true".equals(param); - return new WriterParameters(prettyPrint, depth); + wpBuilder.setPrettyPrint("true".equals(param)); + return wpBuilder.build(); } } diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.java index 38d41ddb2e..6a7267742e 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.java @@ -194,7 +194,7 @@ public class RestconfImpl implements RestconfService { return new NormalizedNodeContext(new InstanceIdentifierContext<>(null, modulesSchemaNode, null, schemaContext), moduleContainerBuilder.build(), - QueryParametersParser.parseKnownWriterParameters(uriInfo)); + QueryParametersParser.parseWriterParameters(uriInfo)); } /** @@ -226,7 +226,7 @@ public class RestconfImpl implements RestconfService { return new NormalizedNodeContext(new InstanceIdentifierContext<>(null, modulesSchemaNode, mountPoint, controllerContext.getGlobalSchema()), moduleContainerBuilder.build(), - QueryParametersParser.parseKnownWriterParameters(uriInfo)); + QueryParametersParser.parseWriterParameters(uriInfo)); } @Override @@ -262,7 +262,7 @@ public class RestconfImpl implements RestconfService { Preconditions.checkState(moduleSchemaNode instanceof ListSchemaNode); return new NormalizedNodeContext(new InstanceIdentifierContext<>(null, moduleSchemaNode, mountPoint, - schemaContext), moduleMap, QueryParametersParser.parseKnownWriterParameters(uriInfo)); + schemaContext), moduleMap, QueryParametersParser.parseWriterParameters(uriInfo)); } @Override @@ -291,7 +291,7 @@ public class RestconfImpl implements RestconfService { return new NormalizedNodeContext(new InstanceIdentifierContext<>(null, streamsContainerSchemaNode, null, - schemaContext), streamsContainerBuilder.build(), QueryParametersParser.parseKnownWriterParameters(uriInfo)); + schemaContext), streamsContainerBuilder.build(), QueryParametersParser.parseWriterParameters(uriInfo)); } @Override @@ -462,7 +462,7 @@ public class RestconfImpl implements RestconfService { return new NormalizedNodeContext(new InstanceIdentifierContext(null, resultNodeSchema, mountPoint, schemaContext), resultData, - QueryParametersParser.parseKnownWriterParameters(uriInfo)); + QueryParametersParser.parseWriterParameters(uriInfo)); } private DOMRpcResult checkRpcResponse(final CheckedFuture response) { @@ -640,7 +640,7 @@ public class RestconfImpl implements RestconfService { } return new NormalizedNodeContext(new InstanceIdentifierContext<>(null, resultNodeSchema, mountPoint, - schemaContext), resultData, QueryParametersParser.parseKnownWriterParameters(uriInfo)); + schemaContext), resultData, QueryParametersParser.parseWriterParameters(uriInfo)); } private RpcDefinition findRpc(final SchemaContext schemaContext, final String identifierDecoded) { @@ -678,7 +678,7 @@ public class RestconfImpl implements RestconfService { LOG.debug(errMsg + identifier); throw new RestconfDocumentedException(errMsg, ErrorType.APPLICATION, ErrorTag.DATA_MISSING); } - return new NormalizedNodeContext(iiWithData, data, QueryParametersParser.parseKnownWriterParameters(uriInfo)); + return new NormalizedNodeContext(iiWithData, data, QueryParametersParser.parseWriterParameters(uriInfo)); } @Override @@ -697,7 +697,7 @@ public class RestconfImpl implements RestconfService { LOG.debug(errMsg + identifier); throw new RestconfDocumentedException(errMsg , ErrorType.APPLICATION, ErrorTag.DATA_MISSING); } - return new NormalizedNodeContext(iiWithData, data, QueryParametersParser.parseKnownWriterParameters(uriInfo)); + return new NormalizedNodeContext(iiWithData, data, QueryParametersParser.parseWriterParameters(uriInfo)); } @Override diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/WriterParameters.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/WriterParameters.java index 9b26f60dbf..45df2f191c 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/WriterParameters.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/WriterParameters.java @@ -1,20 +1,52 @@ package org.opendaylight.controller.sal.restconf.impl; +import com.google.common.base.Optional; + public class WriterParameters { - private final int depth; + private final Optional depth; private final boolean prettyPrint; - public WriterParameters(final boolean prettyPrint, final int depth) { - this.prettyPrint = prettyPrint; - this.depth = depth; + private WriterParameters(final WriterParametersBuilder builder) { + this.prettyPrint = builder.prettyPrint; + this.depth = builder.depth; } - public int getDepth() { + public Optional getDepth() { return depth; } public boolean isPrettyPrint() { return prettyPrint; } + + public static class WriterParametersBuilder { + private Optional depth = Optional.absent(); + private boolean prettyPrint; + + public WriterParametersBuilder() { + } + + public Optional getDepth() { + return depth; + } + + public WriterParametersBuilder setDepth(final int depth) { + this.depth = Optional.of(depth); + return this; + } + + public boolean isPrettyPrint() { + return prettyPrint; + } + + public WriterParametersBuilder setPrettyPrint(final boolean prettyPrint) { + this.prettyPrint = prettyPrint; + return this; + } + + public WriterParameters build() { + return new WriterParameters(this); + } + } } diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/CutDataToCorrectDepthTest.java b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/CutDataToCorrectDepthTest.java new file mode 100644 index 0000000000..6684bec334 --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/CutDataToCorrectDepthTest.java @@ -0,0 +1,378 @@ +/* + * 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.controller.sal.restconf.impl.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import javax.ws.rs.Consumes; +import javax.ws.rs.Encoded; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.client.Entity; +import javax.ws.rs.core.Application; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.junit.BeforeClass; +import org.opendaylight.controller.sal.rest.impl.JsonNormalizedNodeBodyReader; +import org.opendaylight.controller.sal.rest.impl.NormalizedNodeJsonBodyWriter; +import org.opendaylight.controller.sal.rest.impl.NormalizedNodeXmlBodyWriter; +import org.opendaylight.controller.sal.rest.impl.RestconfDocumentedExceptionMapper; +import org.opendaylight.controller.sal.rest.impl.XmlNormalizedNodeBodyReader; +import org.opendaylight.controller.sal.restconf.impl.ControllerContext; +import org.opendaylight.controller.sal.restconf.impl.InstanceIdentifierContext; +import org.opendaylight.controller.sal.restconf.impl.NormalizedNodeContext; +import org.opendaylight.controller.sal.restconf.impl.QueryParametersParser; +import org.opendaylight.controller.sal.restconf.impl.WriterParameters; +import org.opendaylight.yangtools.yang.common.QName; +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.schema.ContainerNode; +import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; +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.UnkeyedListEntryNode; +import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListNode; +import org.opendaylight.yangtools.yang.data.impl.schema.Builders; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeAttrBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.ListNodeBuilder; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; + +public class CutDataToCorrectDepthTest extends JerseyTest { + + private static NormalizedNode depth1Cont; + private static NormalizedNode depth2Cont1; + private NormalizedNode globalPayload; + private static SchemaContext schemaContextModules; + + @Path("/") + public class RestImpl { + + @GET + @Path("/config/{identifier:.+}") + @Produces({ "application/json", "application/xml" }) + public NormalizedNodeContext getData(@Encoded @PathParam("identifier") String identifier, + @Context UriInfo uriInfo) { + + final InstanceIdentifierContext iiWithData = ControllerContext.getInstance().toInstanceIdentifier( + identifier); + + NormalizedNode data = null; + if (identifier.equals("nested-module:depth1-cont/depth2-cont1")) { + data = depth2Cont1; + } else if (identifier.equals("nested-module:depth1-cont")) { + data = depth1Cont; + } + + final WriterParameters writerParameters = QueryParametersParser.parseWriterParameters(uriInfo); + return new NormalizedNodeContext(iiWithData, data, writerParameters); + } + + @GET + @Path("/operational/{identifier:.+}") + @Produces({ "application/json", "application/xml" }) + public NormalizedNodeContext getDataOperational(@Encoded @PathParam("identifier") String identifier, + @Context UriInfo uriInfo) { + return getData(identifier, uriInfo); + } + + @PUT + @Path("/config/{identifier:.+}") + @Consumes({ "application/json", "application/xml" }) + public void normalizedData(@Encoded @PathParam("identifier") String identifier, NormalizedNodeContext payload) throws InterruptedException { + System.out.println(payload); + System.out.println(payload.getInstanceIdentifierContext().getInstanceIdentifier()); + System.out.println(payload.getData()); + globalPayload = payload.getData(); + } + + @PUT + @Path("/operational/{identifier:.+}") + @Consumes({ "application/json", "application/xml" }) + public void normalizedDataOperational(@Encoded @PathParam("identifier") String identifier, + NormalizedNodeContext payload) throws InterruptedException { + normalizedData(identifier, payload); + } + } + + @BeforeClass + public static void initialize() throws FileNotFoundException { + schemaContextModules = TestUtils.loadSchemaContext("/modules"); + Module module = TestUtils.findModule(schemaContextModules.getModules(), "nested-module"); + assertNotNull(module); + + UnkeyedListNode listAsUnkeyedList = unkeyedList( + "depth2-cont1", + unkeyedEntry("depth2-cont1", + container("depth3-cont1", + container("depth4-cont1", leaf("depth5-leaf1", "depth5-leaf1-value")), + leaf("depth4-leaf1", "depth4-leaf1-value")), leaf("depth3-leaf1", "depth3-leaf1-value"))); + + MapNode listAsMap = mapNode( + "depth2-list2", + mapEntryNode("depth2-list2", 2, leaf("depth3-lf1-key", "depth3-lf1-key-value"), + leaf("depth3-lf2-key", "depth3-lf2-key-value"), leaf("depth3-lf3", "depth3-lf3-value"))); + + depth1Cont = container( + "depth1-cont", + listAsUnkeyedList, + listAsMap, + leafList("depth2-lfLst1", "depth2-lflst1-value1", "depth2-lflst1-value2", "depth2-lflst1-value3"), + container( + "depth2-cont2", + container("depth3-cont2", + container("depth4-cont2", leaf("depth5-leaf2", "depth5-leaf2-value")), + leaf("depth4-leaf2", "depth4-leaf2-value")), leaf("depth3-leaf2", "depth3-leaf2-value")), + leaf("depth2-leaf1", "depth2-leaf1-value")); + + depth2Cont1 = listAsUnkeyedList; + } + + // TODO: These tests should be fixed/rewriten because they fail randomly due to data not being de-serialized + // properly in readers + //@Test + public void getDataWithUriDepthParameterTest() throws WebApplicationException, IOException { + getDataWithUriDepthParameter("application/json"); + getDataWithUriDepthParameter("application/xml"); + } + + public void getDataWithUriDepthParameter(final String mediaType) throws WebApplicationException, IOException { + ControllerContext.getInstance().setGlobalSchema(schemaContextModules); + Response response; + + // Test config with depth 1 + response = target("/config/nested-module:depth1-cont").queryParam("depth", "1").request(mediaType) + .get(); + txtDataToNormalizedNode(response, mediaType, "/config/nested-module:depth1-cont"); + verifyResponse(nodeDataDepth1()); + + // Test config with depth 2 + response = target("/config/nested-module:depth1-cont").queryParam("depth", "2").request(mediaType) + .get(); + txtDataToNormalizedNode(response, mediaType, "/config/nested-module:depth1-cont"); + verifyResponse(nodeDataDepth2()); + + // Test config with depth 3 + response = target("/config/nested-module:depth1-cont").queryParam("depth", "3").request(mediaType) + .get(); + txtDataToNormalizedNode(response, mediaType, "/config/nested-module:depth1-cont"); + verifyResponse(nodeDataDepth3()); + + // Test config with depth 4 + response = target("/config/nested-module:depth1-cont").queryParam("depth", "4").request(mediaType) + .get(); + txtDataToNormalizedNode(response, mediaType, "/config/nested-module:depth1-cont"); + verifyResponse(nodeDataDepth4()); + + // Test config with depth 5 + response = target("/config/nested-module:depth1-cont").queryParam("depth", "5").request(mediaType) + .get(); + txtDataToNormalizedNode(response, mediaType, "/config/nested-module:depth1-cont"); + verifyResponse(nodeDataDepth5()); + + // Test config with depth unbounded + + response = target("/config/nested-module:depth1-cont").queryParam("depth", "unbounded") + .request(mediaType).get(); + txtDataToNormalizedNode(response, mediaType, "/config/nested-module:depth1-cont"); + verifyResponse(nodeDataDepth5()); + } + + private void txtDataToNormalizedNode(final Response response, final String mediaType, final String uri) { + String responseStr = response.readEntity(String.class); + System.out.println(responseStr); + target(uri).request(mediaType).put(Entity.entity(responseStr, mediaType)); + } + + private void verifyResponse(final NormalizedNode nodeData) throws WebApplicationException, IOException { + assertNotNull(globalPayload); + assertEquals(globalPayload, nodeData); + globalPayload = null; + } + + @Override + protected Application configure() { + ResourceConfig resourceConfig = new ResourceConfig(); + resourceConfig = resourceConfig.registerInstances(new RestImpl()); + resourceConfig.registerClasses(XmlNormalizedNodeBodyReader.class, NormalizedNodeXmlBodyWriter.class, + JsonNormalizedNodeBodyReader.class, NormalizedNodeJsonBodyWriter.class, + RestconfDocumentedExceptionMapper.class); + return resourceConfig; + } + + private static LeafNode leaf(final String localName, final Object value) { + return Builders.leafBuilder().withNodeIdentifier(toIdentifier(localName)).withValue(value).build(); + } + + private static ContainerNode container(final String localName, final DataContainerChild... children) { + DataContainerNodeAttrBuilder containerBuilder = Builders.containerBuilder(); + for (DataContainerChild child : children) { + containerBuilder.withChild(child); + } + containerBuilder.withNodeIdentifier(toIdentifier(localName)); + return containerBuilder.build(); + } + + private static UnkeyedListNode unkeyedList( + final String localName, + final UnkeyedListEntryNode... entryNodes) { + CollectionNodeBuilder builder = Builders.unkeyedListBuilder(); + final NodeIdentifier identifier = toIdentifier(localName); + builder.withNodeIdentifier(identifier); + for (UnkeyedListEntryNode unkeyedListEntryNode : entryNodes) { + builder.withChild(unkeyedListEntryNode); + } + return builder.build(); + } + + private static UnkeyedListEntryNode unkeyedEntry(final String localName, + final DataContainerChild... children) { + DataContainerNodeAttrBuilder builder = Builders.unkeyedListEntryBuilder(); + builder.withNodeIdentifier(toIdentifier(localName)); + for (DataContainerChild child : children) { + builder.withChild(child); + } + return builder.build(); + } + + private static MapNode mapNode(final String localName, final MapEntryNode... entryNodes) { + CollectionNodeBuilder builder = Builders.mapBuilder(); + builder.withNodeIdentifier(toIdentifier(localName)); + for (MapEntryNode mapEntryNode : entryNodes) { + builder.withChild(mapEntryNode); + } + return builder.build(); + } + + private static MapEntryNode mapEntryNode(final String localName, final int keysNumber, + final DataContainerChild... children) { + DataContainerNodeAttrBuilder builder = Builders.mapEntryBuilder(); + Map keys = new HashMap<>(); + for (int i = 0; i < keysNumber; i++) { + keys.put(children[i].getNodeType(), children[i].getValue()); + } + builder.withNodeIdentifier(toIdentifier(localName, keys)); + + for (DataContainerChild child : children) { + builder.withChild(child); + } + return builder.build(); + } + + private static LeafSetNode leafList(final String localName, final String... children) { + ListNodeBuilder> builder = Builders.leafSetBuilder(); + builder.withNodeIdentifier(toIdentifier(localName)); + for (String child : children) { + builder.withChild(Builders.leafSetEntryBuilder().withNodeIdentifier(toIdentifier(localName, child)) + .withValue(child).build()); + } + return builder.build(); + } + + private static NodeIdentifier toIdentifier(String localName) { + return new NodeIdentifier(QName.create("urn:nested:module", "2014-06-3", localName)); + } + + private static NodeIdentifierWithPredicates toIdentifier(String localName, Map keys) { + return new NodeIdentifierWithPredicates(QName.create("urn:nested:module", "2014-06-3", localName), + keys); + } + + private static NodeWithValue toIdentifier(final String localName, final Object value) { + return new NodeWithValue(QName.create("urn:nested:module", "2014-06-3", localName), value); + } + + + + private UnkeyedListEntryNode nodeDataDepth3Operational() { + return unkeyedEntry("depth2-cont1", + container("depth3-cont1", container("depth4-cont1"), leaf("depth4-leaf1", "depth4-leaf1-value")), + leaf("depth3-leaf1", "depth3-leaf1-value")); + } + + private ContainerNode nodeDataDepth5() { + return container( + "depth1-cont", + unkeyedList( + "depth2-cont1", + unkeyedEntry("depth2-cont1", + container("depth3-cont1", + container("depth4-cont1", leaf("depth5-leaf1", "depth5-leaf1-value")), + leaf("depth4-leaf1", "depth4-leaf1-value")), + leaf("depth3-leaf1", "depth3-leaf1-value"))), + mapNode("depth2-list2", + mapEntryNode("depth2-list2", 2, leaf("depth3-lf1-key", "depth3-lf1-key-value"), + leaf("depth3-lf2-key", "depth3-lf2-key-value"), leaf("depth3-lf3", "depth3-lf3-value"))), + leafList("depth2-lfLst1", "depth2-lflst1-value1", "depth2-lflst1-value2", "depth2-lflst1-value3"), + container( + "depth2-cont2", + container("depth3-cont2", + container("depth4-cont2", leaf("depth5-leaf2", "depth5-leaf2-value")), + leaf("depth4-leaf2", "depth4-leaf2-value")), leaf("depth3-leaf2", "depth3-leaf2-value")), + leaf("depth2-leaf1", "depth2-leaf1-value")); + } + + private ContainerNode nodeDataDepth4() { + return container( + "depth1-cont", + unkeyedList("depth2-cont1", nodeDataDepth3Operational()), + mapNode("depth2-list2", + mapEntryNode("depth2-list2", 2, leaf("depth3-lf1-key", "depth3-lf1-key-value"), + leaf("depth3-lf2-key", "depth3-lf2-key-value"), leaf("depth3-lf3", "depth3-lf3-value"))), + leafList("depth2-lfLst1", "depth2-lflst1-value1", "depth2-lflst1-value2", "depth2-lflst1-value3"), + container( + "depth2-cont2", + container("depth3-cont2", container("depth4-cont2"), leaf("depth4-leaf2", "depth4-leaf2-value")), + leaf("depth3-leaf2", "depth3-leaf2-value")), leaf("depth2-leaf1", "depth2-leaf1-value")); + } + + private ContainerNode nodeDataDepth3() { + return container( + "depth1-cont", + unkeyedList("depth2-cont1", + unkeyedEntry("depth2-cont1", container("depth3-cont1"), leaf("depth3-leaf1", "depth3-leaf1-value"))), + mapNode("depth2-list2", + mapEntryNode("depth2-list2", 2, leaf("depth3-lf1-key", "depth3-lf1-key-value"), + leaf("depth3-lf2-key", "depth3-lf2-key-value"), leaf("depth3-lf3", "depth3-lf3-value"))), + leafList("depth2-lfLst1", "depth2-lflst1-value1", "depth2-lflst1-value2", "depth2-lflst1-value3"), + container("depth2-cont2", container("depth3-cont2"), leaf("depth3-leaf2", "depth3-leaf2-value")), + leaf("depth2-leaf1", "depth2-leaf1-value")); + } + + private ContainerNode nodeDataDepth2() { + return container( + "depth1-cont", + unkeyedList("depth2-cont1", unkeyedEntry("depth2-cont1")), + mapNode("depth2-list2", + mapEntryNode("depth2-list2", 2, leaf("depth3-lf1-key", "depth3-lf1-key-value"), + leaf("depth3-lf2-key", "depth3-lf2-key-value"))), container("depth2-cont2"), +// leafList("depth2-lfLst1"), + leaf("depth2-leaf1", "depth2-leaf1-value")); + } + + private ContainerNode nodeDataDepth1() { + return container("depth1-cont"); + } +} diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/modules/nested-module.yang b/opendaylight/md-sal/sal-rest-connector/src/test/resources/modules/nested-module.yang index 590743c9ca..726b8d9a5b 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/test/resources/modules/nested-module.yang +++ b/opendaylight/md-sal/sal-rest-connector/src/test/resources/modules/nested-module.yang @@ -4,24 +4,42 @@ module nested-module { revision "2014-06-3"; container depth1-cont { - container depth2-cont1 { + list depth2-cont1 { container depth3-cont1 { container depth4-cont1 { leaf depth5-leaf1 { type string; } } - + leaf depth4-leaf1 { type string; } } - + leaf depth3-leaf1 { type string; } } - + + /* list depth2-list2 was added to test keyed list */ + list depth2-list2 { + key "depth3-lf1-key depth3-lf2-key"; + leaf depth3-lf1-key { + type string; + } + leaf depth3-lf2-key { + type string; + } + leaf depth3-lf3 { + type string; + } + } + + leaf-list depth2-lfLst1 { + type string; + } + container depth2-cont2 { container depth3-cont2 { container depth4-cont2 { @@ -29,19 +47,19 @@ module nested-module { type string; } } - + leaf depth4-leaf2 { type string; } } - + leaf depth3-leaf2 { type string; } } - + leaf depth2-leaf1 { type string; } } -} \ No newline at end of file +}