Bug 8988 - Check for empty payload properly 48/63048/1
authorJakub Morvay <jmorvay@cisco.com>
Tue, 12 Sep 2017 14:48:23 +0000 (16:48 +0200)
committerJakub Morvay <jmorvay@cisco.com>
Tue, 12 Sep 2017 14:48:23 +0000 (16:48 +0200)
Do not use InputStream#avalaible method to check for empty payload's
input streams in our implementations of MessageBodyReader interface.
InputStream#avalaible method returns an estimate of the number of bytes
that can be read from input stream without blocking. We cannot be sure
the stream is actually empty, even if this method returns 0.

Wrap the payload's input stream in PushbackInputStream and try to read
and unread from it instead.

Change-Id: I43b3a8d837f3dc4bc59d7cc2db29ffa06786844f
Signed-off-by: Jakub Morvay <jmorvay@cisco.com>
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/JsonNormalizedNodeBodyReader.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/JsonToPatchBodyReader.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/RestUtil.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/XmlNormalizedNodeBodyReader.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/XmlToPatchBodyReader.java
restconf/sal-rest-connector/src/main/java/org/opendaylight/restconf/jersey/providers/AbstractIdentifierAwareJaxRsProvider.java

index db2b3188322432d0e8768a227c79beea52ee370f..fad20b3448c14e7fafd7b007aa3ceb4daee18e81 100644 (file)
@@ -16,6 +16,7 @@ import java.lang.annotation.Annotation;
 import java.lang.reflect.Type;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.MediaType;
@@ -101,7 +102,8 @@ public class JsonNormalizedNodeBodyReader
     private static NormalizedNodeContext readFrom(final InstanceIdentifierContext<?> path,
                                                   final InputStream entityStream, final boolean isPost)
             throws IOException {
-        if (entityStream.available() < 1) {
+        final Optional<InputStream> nonEmptyInputStreamOptional = RestUtil.isInputStreamEmpty(entityStream);
+        if (!nonEmptyInputStreamOptional.isPresent()) {
             return new NormalizedNodeContext(path, null);
         }
         final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
@@ -123,7 +125,7 @@ public class JsonNormalizedNodeBodyReader
         }
 
         final JsonParserStream jsonParser = JsonParserStream.create(writer, path.getSchemaContext(), parentSchema);
-        final JsonReader reader = new JsonReader(new InputStreamReader(entityStream));
+        final JsonReader reader = new JsonReader(new InputStreamReader(nonEmptyInputStreamOptional.get()));
         jsonParser.parse(reader);
 
         NormalizedNode<?, ?> result = resultHolder.getResult();
index ac4d233bf17a0f419e76f0a0d7a69698520d78c9..4516c22a68513fb610cfa9c94042681319266c53 100644 (file)
@@ -19,6 +19,7 @@ import java.lang.annotation.Annotation;
 import java.lang.reflect.Type;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 import javax.annotation.Nonnull;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.WebApplicationException;
@@ -96,11 +97,12 @@ public class JsonToPatchBodyReader extends AbstractIdentifierAwareJaxRsProvider
 
     private PatchContext readFrom(final InstanceIdentifierContext<?> path, final InputStream entityStream)
             throws IOException {
-        if (entityStream.available() < 1) {
+        final Optional<InputStream> nonEmptyInputStreamOptional = RestUtil.isInputStreamEmpty(entityStream);
+        if (!nonEmptyInputStreamOptional.isPresent()) {
             return new PatchContext(path, null, null);
         }
 
-        final JsonReader jsonReader = new JsonReader(new InputStreamReader(entityStream));
+        final JsonReader jsonReader = new JsonReader(new InputStreamReader(nonEmptyInputStreamOptional.get()));
         final List<PatchEntity> resultList = read(jsonReader, path);
         jsonReader.close();
 
index 4b65d41e4a747ff8e401f24be3d270ca1acec1b8..fd18199b4da54a751d03c3586fb5d9904b2bdb40 100644 (file)
@@ -7,8 +7,13 @@
  */
 package org.opendaylight.netconf.sal.rest.impl;
 
+import com.google.common.base.Preconditions;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PushbackInputStream;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import javax.xml.stream.events.StartElement;
@@ -33,6 +38,30 @@ public final class RestUtil {
         return superType;
     }
 
+    /**
+     * Utility method to find out if is provided {@link InputStream} empty.
+     *
+     * @param  entityStream {@link InputStream} to be checked if it is empty.
+     * @return Empty Optional if provided input stream is empty or Optional
+     *         containing input stream otherwise.
+     *
+     * @throws IOException if an IO error arises during stream inspection.
+     * @throws NullPointerException if provided stream is null.
+     *
+     */
+    public static Optional<InputStream> isInputStreamEmpty(final InputStream entityStream) throws IOException {
+        Preconditions.checkNotNull(entityStream);
+        final PushbackInputStream pushbackInputStream = new PushbackInputStream(entityStream);
+
+        int firstByte = pushbackInputStream.read();
+        if (firstByte == -1) {
+            return Optional.empty();
+        } else {
+            pushbackInputStream.unread(firstByte);
+            return Optional.of(pushbackInputStream);
+        }
+    }
+
     public static IdentityValuesDTO asInstanceIdentifier(final String value, final PrefixesMaping prefixMap) {
         final String valueTrimmed = value.trim();
         if (!valueTrimmed.startsWith("/")) {
index 21bc6d301d5d94a29e2dc4b378ab3098c83ae7b7..f4a699c7ac471b2f635923284a5e9a6d30eb624d 100644 (file)
@@ -19,6 +19,7 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Deque;
 import java.util.List;
+import java.util.Optional;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.core.MediaType;
@@ -104,13 +105,13 @@ public class XmlNormalizedNodeBodyReader extends AbstractIdentifierAwareJaxRsPro
     private NormalizedNodeContext readFrom(final InputStream entityStream) throws IOException, SAXException,
             XMLStreamException, ParserConfigurationException, URISyntaxException {
         final InstanceIdentifierContext<?> path = getInstanceIdentifierContext();
-
-        if (entityStream.available() < 1) {
+        final Optional<InputStream> nonEmptyInputStreamOptional = RestUtil.isInputStreamEmpty(entityStream);
+        if (!nonEmptyInputStreamOptional.isPresent()) {
             // represent empty nopayload input
             return new NormalizedNodeContext(path, null);
         }
 
-        final Document doc = UntrustedXML.newDocumentBuilder().parse(entityStream);
+        final Document doc = UntrustedXML.newDocumentBuilder().parse(nonEmptyInputStreamOptional.get());
         return parse(path, doc);
     }
 
index 8b01c3c5986cf665a7dcfdbd506e8341b0c791b5..8acf89ac438f3f44ee10d6dff0d395ff122c1139 100644 (file)
@@ -19,6 +19,7 @@ import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Optional;
 import javax.annotation.Nonnull;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.WebApplicationException;
@@ -91,13 +92,13 @@ public class XmlToPatchBodyReader extends AbstractIdentifierAwareJaxRsProvider i
 
         try {
             final InstanceIdentifierContext<?> path = getInstanceIdentifierContext();
-
-            if (entityStream.available() < 1) {
+            final Optional<InputStream> nonEmptyInputStreamOptional = RestUtil.isInputStreamEmpty(entityStream);
+            if (!nonEmptyInputStreamOptional.isPresent()) {
                 // represent empty nopayload input
                 return new PatchContext(path, null, null);
             }
 
-            final Document doc = UntrustedXML.newDocumentBuilder().parse(entityStream);
+            final Document doc = UntrustedXML.newDocumentBuilder().parse(nonEmptyInputStreamOptional.get());
             return parse(path, doc);
         } catch (final RestconfDocumentedException e) {
             throw e;
index 8fa4c7e6021aea82908e6040b8f1ccead06cef0b..583ea44ef24b8c0f2cb859cc04bce4ba2aa45160 100644 (file)
@@ -11,6 +11,7 @@ package org.opendaylight.restconf.jersey.providers;
 import com.google.common.base.Optional;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.PushbackInputStream;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Type;
 import javax.ws.rs.HttpMethod;
@@ -47,11 +48,17 @@ public abstract class AbstractIdentifierAwareJaxRsProvider<T> implements Message
             final MultivaluedMap<String, String> httpHeaders, final InputStream entityStream) throws IOException,
             WebApplicationException {
         final InstanceIdentifierContext<?> path = getInstanceIdentifierContext();
-        if (entityStream.available() < 1) {
+
+        final PushbackInputStream pushbackInputStream = new PushbackInputStream(entityStream);
+
+        int firstByte = pushbackInputStream.read();
+        if (firstByte == -1) {
             return emptyBody(path);
+        } else {
+            pushbackInputStream.unread(firstByte);
+            return readBody(path, pushbackInputStream);
         }
 
-        return readBody(path, entityStream);
     }
 
     /**