--- /dev/null
+/*
+ * 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();
+ }
+ }
+}
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;
public enum JsonToCompositeNodeProvider implements MessageBodyReader<CompositeNode> {
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<CompositeNode> type, final Type genericType, final Annotation[] annotations,
- final MediaType mediaType, final MultivaluedMap<String, String> httpHeaders, final InputStream entityStream)
- throws IOException, WebApplicationException {
+ public CompositeNode readFrom(final Class<CompositeNode> type, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType,
+ final MultivaluedMap<String, String> 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);
}
}
-
}
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;
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;
}
throw new UnsupportedFormatException("Root element of Json has to be Object");
}
- Set<Entry<String, JsonElement>> entrySetsOfRootJsonObject = rootElement.getAsJsonObject().entrySet();
+ Set<Entry<String, JsonElement>> entrySetsOfRootJsonObject =
+ rootElement.getAsJsonObject().entrySet();
if (entrySetsOfRootJsonObject.size() != 1) {
throw new UnsupportedFormatException("Json Object should contain one element");
}
"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<String, JsonElement> childOfFirstNode : rootObject.entrySet()) {
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<String, JsonElement> childOfChild : childType.getAsJsonObject().entrySet()) {
addChildToParent(childOfChild.getKey(), childOfChild.getValue(), child);
} 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);
// 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;
}
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.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;
public enum XmlToCompositeNodeProvider implements MessageBodyReader<CompositeNode> {
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) {
public CompositeNode readFrom(Class<CompositeNode> type, Type genericType, Annotation[] annotations,
MediaType mediaType, MultivaluedMap<String, String> 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);
}
}
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;
}
}
/**
- * 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();
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;
}
}
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;
}
}
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;
}
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;
}
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"
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;
final DataNodeContainer schema, final MountInstance mountPoint,
final QName currentAugment ) {
final List<NodeWrapper<?>> children = compositeNodeBuilder.getValues();
+ checkNodeMultiplicityAccordingToSchema(schema,children);
for (final NodeWrapper<? extends Object> child : children) {
final List<DataSchemaNode> potentialSchemaNodes =
this.controllerContext.findInstanceDataChildrenByName(
}
}
+ private void checkNodeMultiplicityAccordingToSchema(final DataNodeContainer dataNodeContainer,
+ final List<NodeWrapper<?>> nodes) {
+ Map<String, Integer> equalNodeNamesToCounts = new HashMap<String, Integer>();
+ 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<? extends Object> nodeBuilder,
final DataSchemaNode schema, final QName previousAugment,
final MountInstance mountPoint) {
--- /dev/null
+/*
+ * 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<CompositeNode> messageBodyReader) {
+ try {
+ CompositeNode compositeNode = TestUtils.readInputToCnSn(path, false,messageBodyReader);
+ assertNotNull(compositeNode);
+
+ Set<Module> 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<RestconfError> errors = e.getErrors();
+ assertNotNull(errors);
+
+ assertEquals(1, errors.size());
+
+ RestconfError restconfError = errors.get(0);
+
+ assertEquals(errorType, restconfError.getErrorType());
+ assertEquals(errorTag, restconfError.getErrorTag());
+ }
+ }
+
+}
--- /dev/null
+{
+ "cont":{
+ "cont1":[
+ {
+ "lst11":{
+ "lf111":"value1"
+ }
+ },
+ {
+ "lst11":{
+ "lf111":"value2"
+ }
+ }
+ ]
+ }
+}
\ No newline at end of file
--- /dev/null
+<cont>
+ <cont1>
+ <lst11>
+ <lf111>value1</lf111>
+ </lst11>
+ </cont1>
+ <cont1>
+ <lst11>
+ <lf111>value1</lf111>
+ </lst11>
+ </cont1>
+</cont>
\ No newline at end of file
--- /dev/null
+{
+ "cont":{
+ "cont1":{
+ "lst11":[
+ {
+ "lf111":"value1",
+ "lf111":"value2"
+ },
+ {
+ "lf111":"value3"
+ }
+ ]
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<cont>
+ <cont1>
+ <lst11>
+ <lf111>value1</lf111>,
+ <lf111>value2</lf111>,
+ </lst11>
+ <lst11>
+ <lf111>value3</lf111>,
+ </lst11>
+ </cont1>
+</cont>
\ No newline at end of file
--- /dev/null
+/* 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
{
- "cont": {
- "lf":
- }
+ "cont": {
+ "lf":
+ }
}
\ No newline at end of file
--- /dev/null
+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