From d791a261edf5d47ba1860acb25c82b4919e2d3a5 Mon Sep 17 00:00:00 2001 From: Jozef Gloncak Date: Tue, 24 Jun 2014 08:13:53 +0200 Subject: [PATCH] BUG 1204 - exception if multiple container node occures Container schema node + multiple data nodes -> exception RestconfDocumentedException is raised. List schema node + one data node (e. g. in json is specified as container "list-name":{} and not "list-name":[{},{}...,{}] -> this is acceptable case because list can have even only one entry. Leaf schema node + multiple data nodes -> special check whether at equal objects is everytime uniqe key. If it isn't so then RestconfDocumentedException is raised. For Json specific JsonParser was implemented which is catch duplicity at object level an time of parsing. For XML this duplicity name check is done during normalization phase. Change-Id: I0b544c015bcf817e82206276bd24c783093308d6 Signed-off-by: Jozef Gloncak --- .../controller/sal/rest/gson/JsonParser.java | 98 +++++++++++++++++++ .../impl/JsonToCompositeNodeProvider.java | 19 ++-- ...er.java => JsonToCompositeNodeReader.java} | 33 ++++--- .../rest/impl/XmlToCompositeNodeProvider.java | 13 +-- ...der.java => XmlToCompositeNodeReader.java} | 49 +++++----- .../sal/restconf/impl/RestconfImpl.java | 25 +++++ .../MultipleEqualNamesForDataNodesTest.java | 84 ++++++++++++++++ .../equal-name-data-for-container.json | 16 +++ .../equal-name-data-for-container.xml | 12 +++ .../equal-name-data-for-leaf.json | 15 +++ .../equal-name-data-for-leaf.xml | 11 +++ .../yang/equal-data-node-names.yang | 18 ++++ .../resources/json-to-cnsn/empty-data1.json | 6 +- .../multiple-nodes/multiple-nodes.yang | 17 ++++ 14 files changed, 360 insertions(+), 56 deletions(-) create mode 100644 opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/gson/JsonParser.java rename opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/{JsonReader.java => JsonToCompositeNodeReader.java} (86%) rename opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/{XmlReader.java => XmlToCompositeNodeReader.java} (88%) create mode 100644 opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/MultipleEqualNamesForDataNodesTest.java create mode 100644 opendaylight/md-sal/sal-rest-connector/src/test/resources/equal-data-node-names/equal-name-data-for-container.json create mode 100644 opendaylight/md-sal/sal-rest-connector/src/test/resources/equal-data-node-names/equal-name-data-for-container.xml create mode 100644 opendaylight/md-sal/sal-rest-connector/src/test/resources/equal-data-node-names/equal-name-data-for-leaf.json create mode 100644 opendaylight/md-sal/sal-rest-connector/src/test/resources/equal-data-node-names/equal-name-data-for-leaf.xml create mode 100644 opendaylight/md-sal/sal-rest-connector/src/test/resources/equal-data-node-names/yang/equal-data-node-names.yang create mode 100644 opendaylight/md-sal/sal-rest-connector/src/test/resources/multiple-nodes/multiple-nodes.yang diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/gson/JsonParser.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/gson/JsonParser.java new file mode 100644 index 0000000000..f3b61de457 --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/gson/JsonParser.java @@ -0,0 +1,98 @@ +/* + * 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.rest.gson; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonIOException; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSyntaxException; +import com.google.gson.internal.LazilyParsedNumber; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.MalformedJsonException; +import java.io.EOFException; +import java.io.IOException; + +/** + * This class parses JSON elements from a gson JsonReader. It disallows multiple + * elements of the same name unlike the default gson JsonParser." + */ +public class JsonParser { + public JsonElement parse(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; + return read(reader); + } catch (EOFException e) { + if (isEmpty) { + 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 JsonElement read(JsonReader in) throws IOException { + switch (in.peek()) { + case STRING: + return new JsonPrimitive(in.nextString()); + case NUMBER: + String number = in.nextString(); + return new JsonPrimitive(new LazilyParsedNumber(number)); + case BOOLEAN: + return new JsonPrimitive(in.nextBoolean()); + case NULL: + in.nextNull(); + return JsonNull.INSTANCE; + case BEGIN_ARRAY: + JsonArray array = new JsonArray(); + in.beginArray(); + while (in.hasNext()) { + array.add(read(in)); + } + in.endArray(); + return array; + case BEGIN_OBJECT: + JsonObject object = new JsonObject(); + in.beginObject(); + while (in.hasNext()) { + final String childName = in.nextName(); + if (object.has(childName)) { + throw new JsonSyntaxException("Duplicate name " + childName + " in JSON input."); + } + object.add(childName, read(in)); + } + in.endObject(); + return object; + case END_DOCUMENT: + case NAME: + case END_OBJECT: + case END_ARRAY: + default: + throw new IllegalArgumentException(); + } + } +} diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonToCompositeNodeProvider.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonToCompositeNodeProvider.java index 9e69665405..211e7e818a 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonToCompositeNodeProvider.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonToCompositeNodeProvider.java @@ -11,14 +11,12 @@ import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Type; - import javax.ws.rs.Consumes; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.ext.MessageBodyReader; import javax.ws.rs.ext.Provider; - import org.opendaylight.controller.sal.rest.api.Draft02; import org.opendaylight.controller.sal.rest.api.RestconfService; import org.opendaylight.controller.sal.restconf.impl.RestconfDocumentedException; @@ -34,25 +32,28 @@ import org.slf4j.LoggerFactory; public enum JsonToCompositeNodeProvider implements MessageBodyReader { INSTANCE; - private final static Logger LOG = LoggerFactory.getLogger( JsonToCompositeNodeProvider.class ); + private final static Logger LOG = LoggerFactory.getLogger(JsonToCompositeNodeProvider.class); @Override - public boolean isReadable(final Class type, final Type genericType, final Annotation[] annotations, final MediaType mediaType) { + public boolean isReadable(final Class type, final Type genericType, + final Annotation[] annotations, final MediaType mediaType) { return true; } @Override - public CompositeNode readFrom(final Class type, final Type genericType, final Annotation[] annotations, - final MediaType mediaType, final MultivaluedMap httpHeaders, final InputStream entityStream) - throws IOException, WebApplicationException { + public CompositeNode readFrom(final Class type, final Type genericType, + final Annotation[] annotations, final MediaType mediaType, + final MultivaluedMap httpHeaders, + final InputStream entityStream) + throws IOException, WebApplicationException { try { - return JsonReader.read(entityStream); + return JsonToCompositeNodeReader.read(entityStream); } catch (Exception e) { LOG.debug( "Error parsing json input", e); + throw new RestconfDocumentedException( "Error parsing input: " + e.getMessage(), ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE); } } - } diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonReader.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonToCompositeNodeReader.java similarity index 86% rename from opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonReader.java rename to opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonToCompositeNodeReader.java index e945540618..082675b6d1 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonReader.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/JsonToCompositeNodeReader.java @@ -11,16 +11,17 @@ import com.google.common.base.Splitter; import com.google.common.collect.Iterators; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; +import com.google.gson.stream.JsonReader; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URI; -import java.util.Iterator; import java.util.Map.Entry; +import java.util.Iterator; import java.util.Set; +import org.opendaylight.controller.sal.rest.gson.JsonParser; import org.opendaylight.controller.sal.rest.impl.RestUtil.PrefixMapingFromJson; import org.opendaylight.controller.sal.restconf.impl.CompositeNodeWrapper; import org.opendaylight.controller.sal.restconf.impl.EmptyNodeWrapper; @@ -29,20 +30,22 @@ import org.opendaylight.controller.sal.restconf.impl.SimpleNodeWrapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -final class JsonReader { +class JsonToCompositeNodeReader { private static final Logger LOG = LoggerFactory.getLogger(JsonReader.class); private static final Splitter COLON_SPLITTER = Splitter.on(':'); - private JsonReader() { + private JsonToCompositeNodeReader() { } - public static CompositeNodeWrapper read(final InputStream entityStream) throws UnsupportedFormatException { + public static CompositeNodeWrapper read(final InputStream entityStream) + throws UnsupportedFormatException { JsonParser parser = new JsonParser(); - JsonElement rootElement = parser.parse(new InputStreamReader(entityStream)); + JsonElement rootElement = parser.parse(new JsonReader( + new InputStreamReader(entityStream))); if (rootElement.isJsonNull()) { - //no content, so return null to indicate no input + // no content, so return null to indicate no input return null; } @@ -50,7 +53,8 @@ final class JsonReader { throw new UnsupportedFormatException("Root element of Json has to be Object"); } - Set> entrySetsOfRootJsonObject = rootElement.getAsJsonObject().entrySet(); + Set> entrySetsOfRootJsonObject = + rootElement.getAsJsonObject().entrySet(); if (entrySetsOfRootJsonObject.size() != 1) { throw new UnsupportedFormatException("Json Object should contain one element"); } @@ -77,7 +81,8 @@ final class JsonReader { "First element in Json Object has to be \"Object\" or \"Array with one Object element\". Other scenarios are not supported yet."); } - private static CompositeNodeWrapper createStructureWithRoot(final String rootObjectName, final JsonObject rootObject) { + private static CompositeNodeWrapper createStructureWithRoot(final String rootObjectName, + final JsonObject rootObject) { CompositeNodeWrapper firstNode = new CompositeNodeWrapper(getNamespaceFor(rootObjectName), getLocalNameFor(rootObjectName)); for (Entry childOfFirstNode : rootObject.entrySet()) { @@ -86,9 +91,11 @@ final class JsonReader { return firstNode; } - private static void addChildToParent(final String childName, final JsonElement childType, final CompositeNodeWrapper parent) { + private static void addChildToParent(final String childName, final JsonElement childType, + final CompositeNodeWrapper parent) { if (childType.isJsonObject()) { - CompositeNodeWrapper child = new CompositeNodeWrapper(getNamespaceFor(childName), getLocalNameFor(childName)); + CompositeNodeWrapper child = new CompositeNodeWrapper(getNamespaceFor(childName), + getLocalNameFor(childName)); parent.addValue(child); for (Entry childOfChild : childType.getAsJsonObject().entrySet()) { addChildToParent(childOfChild.getKey(), childOfChild.getValue(), child); @@ -96,6 +103,7 @@ final class JsonReader { } else if (childType.isJsonArray()) { if (childType.getAsJsonArray().size() == 1 && childType.getAsJsonArray().get(0).isJsonNull()) { parent.addValue(new EmptyNodeWrapper(getNamespaceFor(childName), getLocalNameFor(childName))); + } else { for (JsonElement childOfChildType : childType.getAsJsonArray()) { addChildToParent(childName, childOfChildType, parent); @@ -145,8 +153,9 @@ final class JsonReader { // it could be identityref Built-In Type URI namespace = getNamespaceFor(value); if (namespace != null) { - return new IdentityValuesDTO(namespace.toString(), getLocalNameFor(value), null,value); + return new IdentityValuesDTO(namespace.toString(), getLocalNameFor(value), null, value); } + // it is not "prefix:value" but just "value" return value; } diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/XmlToCompositeNodeProvider.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/XmlToCompositeNodeProvider.java index bc7473864c..31e9c96462 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/XmlToCompositeNodeProvider.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/XmlToCompositeNodeProvider.java @@ -11,7 +11,6 @@ import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Type; - import javax.ws.rs.Consumes; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; @@ -19,7 +18,6 @@ import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.ext.MessageBodyReader; import javax.ws.rs.ext.Provider; import javax.xml.stream.XMLStreamException; - import org.opendaylight.controller.sal.rest.api.Draft02; import org.opendaylight.controller.sal.rest.api.RestconfService; import org.opendaylight.controller.sal.restconf.impl.RestconfDocumentedException; @@ -35,7 +33,7 @@ import org.slf4j.LoggerFactory; public enum XmlToCompositeNodeProvider implements MessageBodyReader { INSTANCE; - private final static Logger LOG = LoggerFactory.getLogger( XmlToCompositeNodeProvider.class ); + private final static Logger LOG = LoggerFactory.getLogger(XmlToCompositeNodeProvider.class); @Override public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { @@ -46,14 +44,13 @@ public enum XmlToCompositeNodeProvider implements MessageBodyReader type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { - XmlReader xmlReader = new XmlReader(); + XmlToCompositeNodeReader xmlReader = new XmlToCompositeNodeReader(); try { return xmlReader.read(entityStream); } catch (XMLStreamException | UnsupportedFormatException e) { - LOG.debug( "Error parsing json input", e ); - throw new RestconfDocumentedException( - "Error parsing input: " + e.getMessage(), - ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE ); + LOG.debug("Error parsing json input", e); + throw new RestconfDocumentedException("Error parsing input: " + e.getMessage(), ErrorType.PROTOCOL, + ErrorTag.MALFORMED_MESSAGE); } } diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/XmlReader.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/XmlToCompositeNodeReader.java similarity index 88% rename from opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/XmlReader.java rename to opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/XmlToCompositeNodeReader.java index 1d6ca15f3c..7d0690cdc2 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/XmlReader.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/rest/impl/XmlToCompositeNodeReader.java @@ -28,19 +28,19 @@ import org.opendaylight.controller.sal.restconf.impl.NodeWrapper; import org.opendaylight.controller.sal.restconf.impl.SimpleNodeWrapper; import org.opendaylight.yangtools.yang.data.api.Node; -public class XmlReader { +public class XmlToCompositeNodeReader { private final static XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); private XMLEventReader eventReader; - public CompositeNodeWrapper read(InputStream entityStream) throws XMLStreamException, - UnsupportedFormatException, - IOException { - //Get an XML stream which can be marked, and reset, so we can check and see if there is - //any content being provided. + public CompositeNodeWrapper read(InputStream entityStream) throws XMLStreamException, UnsupportedFormatException, + IOException { + // Get an XML stream which can be marked, and reset, so we can check and + // see if there is + // any content being provided. entityStream = getMarkableStream(entityStream); - if( isInputStreamEmpty( entityStream ) ) { + if (isInputStreamEmpty(entityStream)) { return null; } @@ -103,24 +103,24 @@ public class XmlReader { } /** - * If the input stream is not markable, then it wraps the input stream with a buffered stream, - * which is mark able. That way we can check if the stream is empty safely. + * If the input stream is not markable, then it wraps the input stream with + * a buffered stream, which is mark able. That way we can check if the + * stream is empty safely. + * * @param entityStream * @return */ private InputStream getMarkableStream(InputStream entityStream) { - if( !entityStream.markSupported() ) - { - entityStream = new BufferedInputStream( entityStream ); + if (!entityStream.markSupported()) { + entityStream = new BufferedInputStream(entityStream); } return entityStream; } - private boolean isInputStreamEmpty(InputStream entityStream) - throws IOException { + private boolean isInputStreamEmpty(InputStream entityStream) throws IOException { boolean isEmpty = false; - entityStream.mark( 1 ); - if( entityStream.read() == -1 ){ + entityStream.mark(1); + if (entityStream.read() == -1) { isEmpty = true; } entityStream.reset(); @@ -131,7 +131,7 @@ public class XmlReader { checkArgument(event != null, "XML Event cannot be NULL!"); if (event.isStartElement()) { XMLEvent innerEvent = skipCommentsAndWhitespace(); - if ( innerEvent != null && (innerEvent.isCharacters() || innerEvent.isEndElement())) { + if (innerEvent != null && (innerEvent.isCharacters() || innerEvent.isEndElement())) { return true; } } @@ -142,7 +142,7 @@ public class XmlReader { checkArgument(event != null, "XML Event cannot be NULL!"); if (event.isStartElement()) { XMLEvent innerEvent = skipCommentsAndWhitespace(); - if( innerEvent != null ) { + if (innerEvent != null) { if (innerEvent.isStartElement()) { return true; } @@ -152,16 +152,16 @@ public class XmlReader { } private XMLEvent skipCommentsAndWhitespace() throws XMLStreamException { - while( eventReader.hasNext() ) { + while (eventReader.hasNext()) { XMLEvent event = eventReader.peek(); - if( event.getEventType() == XMLStreamConstants.COMMENT ) { + if (event.getEventType() == XMLStreamConstants.COMMENT) { eventReader.nextEvent(); continue; } - if( event.isCharacters() ) { + if (event.isCharacters()) { Characters chars = event.asCharacters(); - if( chars.isWhiteSpace() ) { + if (chars.isWhiteSpace()) { eventReader.nextEvent(); continue; } @@ -235,7 +235,8 @@ public class XmlReader { private Object resolveValueOfElement(String value, StartElement startElement) { // it could be instance-identifier Built-In Type if (value.startsWith("/")) { - IdentityValuesDTO iiValue = RestUtil.asInstanceIdentifier(value, new RestUtil.PrefixMapingFromXml(startElement)); + IdentityValuesDTO iiValue = RestUtil.asInstanceIdentifier(value, new RestUtil.PrefixMapingFromXml( + startElement)); if (iiValue != null) { return iiValue; } @@ -245,7 +246,7 @@ public class XmlReader { if (namespaceAndValue.length == 2) { String namespace = startElement.getNamespaceContext().getNamespaceURI(namespaceAndValue[0]); if (namespace != null && !namespace.isEmpty()) { - return new IdentityValuesDTO(namespace, namespaceAndValue[1], namespaceAndValue[0],value); + return new IdentityValuesDTO(namespace, namespaceAndValue[1], namespaceAndValue[0], value); } } // it is not "prefix:value" but just "value" 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 3834a383db..4e1adbc598 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 @@ -25,6 +25,7 @@ import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.Future; import javax.ws.rs.core.Response; @@ -1136,6 +1137,7 @@ public class RestconfImpl implements RestconfService { final DataNodeContainer schema, final MountInstance mountPoint, final QName currentAugment ) { final List> children = compositeNodeBuilder.getValues(); + checkNodeMultiplicityAccordingToSchema(schema,children); for (final NodeWrapper child : children) { final List potentialSchemaNodes = this.controllerContext.findInstanceDataChildrenByName( @@ -1196,6 +1198,29 @@ public class RestconfImpl implements RestconfService { } } + private void checkNodeMultiplicityAccordingToSchema(final DataNodeContainer dataNodeContainer, + final List> nodes) { + Map equalNodeNamesToCounts = new HashMap(); + for (NodeWrapper child : nodes) { + Integer count = equalNodeNamesToCounts.get(child.getLocalName()); + equalNodeNamesToCounts.put(child.getLocalName(), count == null ? 1 : ++count); + } + + for (DataSchemaNode childSchemaNode : dataNodeContainer.getChildNodes()) { + if (childSchemaNode instanceof ContainerSchemaNode || childSchemaNode instanceof LeafSchemaNode) { + String localName = childSchemaNode.getQName().getLocalName(); + Integer count = equalNodeNamesToCounts.get(localName); + if (count != null && count > 1) { + throw new RestconfDocumentedException( + "Multiple input data elements were specified for '" + + childSchemaNode.getQName().getLocalName() + + "'. The data for this element type can only be specified once.", + ErrorType.APPLICATION, ErrorTag.BAD_ELEMENT); + } + } + } + } + private QName normalizeNodeName(final NodeWrapper nodeBuilder, final DataSchemaNode schema, final QName previousAugment, final MountInstance mountPoint) { diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/MultipleEqualNamesForDataNodesTest.java b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/MultipleEqualNamesForDataNodesTest.java new file mode 100644 index 0000000000..42131026a5 --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/MultipleEqualNamesForDataNodesTest.java @@ -0,0 +1,84 @@ +/* + * 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 static org.junit.Assert.fail; + +import java.util.List; +import java.util.Set; +import javax.ws.rs.ext.MessageBodyReader; +import org.junit.Test; +import org.opendaylight.controller.sal.rest.impl.JsonToCompositeNodeProvider; +import org.opendaylight.controller.sal.rest.impl.XmlToCompositeNodeProvider; +import org.opendaylight.controller.sal.restconf.impl.RestconfDocumentedException; +import org.opendaylight.controller.sal.restconf.impl.RestconfError; +import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag; +import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.model.api.Module; + +/** + * If more then one data element with equal name exists where container or + * leaf schema node should be present the RestconfDocumentedException has to + * be raised + * + * Tests for BUG 1204 + */ +public class MultipleEqualNamesForDataNodesTest { + + @Test + public void multipleEqualNameDataNodeTestForContainerJsonTest() { + multipleEqualNameDataNodeTest("/equal-data-node-names/equal-name-data-for-container.json", ErrorType.APPLICATION, + ErrorTag.BAD_ELEMENT,JsonToCompositeNodeProvider.INSTANCE); + } + + @Test + public void multipleEqualNameDataNodeTestForLeafJsonTest() { + multipleEqualNameDataNodeTest("/equal-data-node-names/equal-name-data-for-leaf.json", ErrorType.PROTOCOL, + ErrorTag.MALFORMED_MESSAGE,JsonToCompositeNodeProvider.INSTANCE); + } + + @Test + public void multipleEqualNameDataNodeTestForContainerXmlTest() { + multipleEqualNameDataNodeTest("/equal-data-node-names/equal-name-data-for-container.xml", ErrorType.APPLICATION, + ErrorTag.BAD_ELEMENT,XmlToCompositeNodeProvider.INSTANCE); + } + + @Test + public void multipleEqualNameDataNodeTestForLeafXmlTest() { + multipleEqualNameDataNodeTest("/equal-data-node-names/equal-name-data-for-leaf.xml", ErrorType.APPLICATION, + ErrorTag.BAD_ELEMENT,XmlToCompositeNodeProvider.INSTANCE); + } + + private void multipleEqualNameDataNodeTest(String path, ErrorType errorType, ErrorTag errorTag,MessageBodyReader messageBodyReader) { + try { + CompositeNode compositeNode = TestUtils.readInputToCnSn(path, false,messageBodyReader); + assertNotNull(compositeNode); + + Set modules = null; + modules = TestUtils.loadModulesFrom("/equal-data-node-names/yang"); + assertNotNull(modules); + + TestUtils.normalizeCompositeNode(compositeNode, modules, "equal-data-node-names" + ":" + "cont"); + fail("Exception RestconfDocumentedException should be raised"); + } catch (RestconfDocumentedException e) { + List errors = e.getErrors(); + assertNotNull(errors); + + assertEquals(1, errors.size()); + + RestconfError restconfError = errors.get(0); + + assertEquals(errorType, restconfError.getErrorType()); + assertEquals(errorTag, restconfError.getErrorTag()); + } + } + +} diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/equal-data-node-names/equal-name-data-for-container.json b/opendaylight/md-sal/sal-rest-connector/src/test/resources/equal-data-node-names/equal-name-data-for-container.json new file mode 100644 index 0000000000..78b097d8e8 --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/test/resources/equal-data-node-names/equal-name-data-for-container.json @@ -0,0 +1,16 @@ +{ + "cont":{ + "cont1":[ + { + "lst11":{ + "lf111":"value1" + } + }, + { + "lst11":{ + "lf111":"value2" + } + } + ] + } +} \ No newline at end of file diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/equal-data-node-names/equal-name-data-for-container.xml b/opendaylight/md-sal/sal-rest-connector/src/test/resources/equal-data-node-names/equal-name-data-for-container.xml new file mode 100644 index 0000000000..26a1f5f21d --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/test/resources/equal-data-node-names/equal-name-data-for-container.xml @@ -0,0 +1,12 @@ + + + + value1 + + + + + value1 + + + \ No newline at end of file diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/equal-data-node-names/equal-name-data-for-leaf.json b/opendaylight/md-sal/sal-rest-connector/src/test/resources/equal-data-node-names/equal-name-data-for-leaf.json new file mode 100644 index 0000000000..d0a9fd4d3a --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/test/resources/equal-data-node-names/equal-name-data-for-leaf.json @@ -0,0 +1,15 @@ +{ + "cont":{ + "cont1":{ + "lst11":[ + { + "lf111":"value1", + "lf111":"value2" + }, + { + "lf111":"value3" + } + ] + } + } +} \ No newline at end of file diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/equal-data-node-names/equal-name-data-for-leaf.xml b/opendaylight/md-sal/sal-rest-connector/src/test/resources/equal-data-node-names/equal-name-data-for-leaf.xml new file mode 100644 index 0000000000..4221c2d5a7 --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/test/resources/equal-data-node-names/equal-name-data-for-leaf.xml @@ -0,0 +1,11 @@ + + + + value1, + value2, + + + value3, + + + \ No newline at end of file diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/equal-data-node-names/yang/equal-data-node-names.yang b/opendaylight/md-sal/sal-rest-connector/src/test/resources/equal-data-node-names/yang/equal-data-node-names.yang new file mode 100644 index 0000000000..3a3653a12b --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/test/resources/equal-data-node-names/yang/equal-data-node-names.yang @@ -0,0 +1,18 @@ +/* bug 1204 */ +module equal-data-node-names { + namespace "ns:equal:data:node:names"; + + prefix "eqdanona"; + revision 2014-06-26 { + } + + container cont { + container cont1 { + list lst11 { + leaf lf111 { + type string; + } + } + } + } +} \ No newline at end of file diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/json-to-cnsn/empty-data1.json b/opendaylight/md-sal/sal-rest-connector/src/test/resources/json-to-cnsn/empty-data1.json index 10d964d644..b886fc9da9 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/test/resources/json-to-cnsn/empty-data1.json +++ b/opendaylight/md-sal/sal-rest-connector/src/test/resources/json-to-cnsn/empty-data1.json @@ -1,5 +1,5 @@ { - "cont": { - "lf": - } + "cont": { + "lf": + } } \ No newline at end of file diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/resources/multiple-nodes/multiple-nodes.yang b/opendaylight/md-sal/sal-rest-connector/src/test/resources/multiple-nodes/multiple-nodes.yang new file mode 100644 index 0000000000..22a1dae8ac --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/test/resources/multiple-nodes/multiple-nodes.yang @@ -0,0 +1,17 @@ +module multiple-nodes { + namespace "multiple:nodes"; + prefix "mod1"; + revision "2014-06-23"; + + container cont { + container cont1 { + leaf lf11 { + type string; + } + } + + leaf lf1 { + type string; + } + } +} \ No newline at end of file -- 2.36.6