From cb3b57dda9301d1f56d2d5bba6a1b2a307857669 Mon Sep 17 00:00:00 2001 From: Markus KARG Date: Thu, 26 Sep 2019 14:42:06 +0200 Subject: [PATCH 1/5] Throwing NoContentException when InputStream is empty Since JAX-RS 2.1 an MBR must react upon an empty stream either with an empty instance or with NoContentException, but Jersey's JSON-B MBR does neither - it simply throws ProcessingException: "In case the entity input stream is empty, the reader is expected to either return a Java representation of a zero-length entity or throw a javax.ws.rs.core.NoContentException in case no zero-length entity representation is defined for the supported Java type." Signed-off-by: Markus KARG --- .../jsonb/internal/JsonBindingProvider.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/media/json-binding/src/main/java/org/glassfish/jersey/jsonb/internal/JsonBindingProvider.java b/media/json-binding/src/main/java/org/glassfish/jersey/jsonb/internal/JsonBindingProvider.java index 3d4ad43aa9..127589a6a3 100644 --- a/media/json-binding/src/main/java/org/glassfish/jersey/jsonb/internal/JsonBindingProvider.java +++ b/media/json-binding/src/main/java/org/glassfish/jersey/jsonb/internal/JsonBindingProvider.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.PushbackInputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Type; @@ -29,6 +30,7 @@ import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.NoContentException; import javax.ws.rs.ext.ContextResolver; import javax.ws.rs.ext.Provider; import javax.ws.rs.ext.Providers; @@ -70,7 +72,24 @@ public Object readFrom(Class type, Type genericType, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { + if (entityStream.markSupported()) { + entityStream.mark(1); + if (entityStream.read() == -1) { + throw new NoContentException("JSON-B cannot process empty input stream"); + } + entityStream.reset(); + } else { + final PushbackInputStream buffer = new PushbackInputStream(entityStream); + final int firstByte = buffer.read(); + if (firstByte == -1) { + throw new NoContentException("JSON-B cannot process empty input stream"); + } + buffer.unread(firstByte); + entityStream = buffer; + } + Jsonb jsonb = getJsonb(type); + try { return jsonb.fromJson(entityStream, genericType); } catch (JsonbException e) { From 1b9ce3157acc4aa8214fdb984463b15d74abe349 Mon Sep 17 00:00:00 2001 From: Markus KARG Date: Sat, 28 Sep 2019 10:07:59 +0000 Subject: [PATCH 2/5] Unit Test for JsonBindingProvider Signed-off-by: Markus KARG --- .../internal/JsonBindingProviderTest.java | 185 ++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 media/json-binding/src/test/java/org/glassfish/jersey/jsonb/internal/JsonBindingProviderTest.java diff --git a/media/json-binding/src/test/java/org/glassfish/jersey/jsonb/internal/JsonBindingProviderTest.java b/media/json-binding/src/test/java/org/glassfish/jersey/jsonb/internal/JsonBindingProviderTest.java new file mode 100644 index 0000000000..dbc202aabe --- /dev/null +++ b/media/json-binding/src/test/java/org/glassfish/jersey/jsonb/internal/JsonBindingProviderTest.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2020 Markus KARG + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.jsonb.internal; + +import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.NoContentException; +import javax.ws.rs.ext.ContextResolver; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.MessageBodyReader; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.Providers; + +import org.junit.Test; + +/** + * Unit Test for {@link JsonBindingProvider}. + * + * @author Markus KARG (markus@headcrashing.eu) + */ +public final class JsonBindingProviderTest { + + @Test(expected = NoContentException.class) + public final void shouldThrowNoContentException() throws IOException { + // given + final Providers providers = new EmptyProviders(); + final MessageBodyReader mbr = (MessageBodyReader) new JsonBindingProvider(providers); + + // when + mbr.readFrom(Foo.class, Foo.class, new Annotation[0], APPLICATION_JSON_TYPE, + new EmptyMultivaluedMap(), new ByteArrayInputStream(new byte[0])); + + // then + // should throw NoContentException + } + + private static final class Foo { + // no members + } + + private static final class EmptyProviders implements Providers { + + @Override + public final MessageBodyReader getMessageBodyReader(final Class type, final Type genericType, + final Annotation[] annotations, final MediaType mediaType) { + return null; + } + + @Override + public final MessageBodyWriter getMessageBodyWriter(final Class type, final Type genericType, + final Annotation[] annotations, final MediaType mediaType) { + return null; + } + + @Override + public final ExceptionMapper getExceptionMapper(final Class type) { + return null; + } + + @Override + public final ContextResolver getContextResolver(final Class contextType, final MediaType mediaType) { + return null; + } + + } + + private static final class EmptyMultivaluedMap implements MultivaluedMap { + + @Override + public final int size() { + return 0; + } + + @Override + public final boolean isEmpty() { + return true; + } + + @Override + public final boolean containsKey(final Object key) { + return false; + } + + @Override + public final boolean containsValue(final Object value) { + return false; + } + + @Override + public final List get(final Object key) { + return null; + } + + @Override + public final List put(final K key, final List value) { + return null; + } + + @Override + public final List remove(final Object key) { + return null; + } + + @Override + public final void putAll(final Map> m) { + } + + @Override + public final void clear() { + } + + @Override + public final Set keySet() { + return Collections.emptySet(); + } + + @Override + public final Collection> values() { + return Collections.emptySet(); + } + + @Override + public final Set>> entrySet() { + return Collections.emptySet(); + } + + @Override + public final void putSingle(final K key, final V value) { + } + + @Override + public final void add(final K key, final V value) { + } + + @Override + public final V getFirst(final K key) { + return null; + } + + @Override + public final void addAll(final K key, final V... newValues) { + } + + @Override + public final void addAll(final K key, final List valueList) { + } + + @Override + public final void addFirst(final K key, final V value) { + } + + @Override + public final boolean equalsIgnoreValueOrder(final MultivaluedMap otherMap) { + return false; + } + + } + +} From bccd9008528477587b2eb340cbaf0a8762bcdadb Mon Sep 17 00:00:00 2001 From: Markus KARG Date: Sat, 28 Sep 2019 22:04:08 +0000 Subject: [PATCH 3/5] Replaced text literals by localization messages Signed-off-by: Markus KARG --- .../glassfish/jersey/jsonb/internal/JsonBindingProvider.java | 4 ++-- .../org/glassfish/jersey/jsonb/localization.properties | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/media/json-binding/src/main/java/org/glassfish/jersey/jsonb/internal/JsonBindingProvider.java b/media/json-binding/src/main/java/org/glassfish/jersey/jsonb/internal/JsonBindingProvider.java index 127589a6a3..c8a1c7eb19 100644 --- a/media/json-binding/src/main/java/org/glassfish/jersey/jsonb/internal/JsonBindingProvider.java +++ b/media/json-binding/src/main/java/org/glassfish/jersey/jsonb/internal/JsonBindingProvider.java @@ -75,14 +75,14 @@ public Object readFrom(Class type, Type genericType, if (entityStream.markSupported()) { entityStream.mark(1); if (entityStream.read() == -1) { - throw new NoContentException("JSON-B cannot process empty input stream"); + throw new NoContentException(LocalizationMessages.ERROR_JSONB_EMPTYSTREAM()); } entityStream.reset(); } else { final PushbackInputStream buffer = new PushbackInputStream(entityStream); final int firstByte = buffer.read(); if (firstByte == -1) { - throw new NoContentException("JSON-B cannot process empty input stream"); + throw new NoContentException(LocalizationMessages.ERROR_JSONB_EMPTYSTREAM()); } buffer.unread(firstByte); entityStream = buffer; diff --git a/media/json-binding/src/main/resources/org/glassfish/jersey/jsonb/localization.properties b/media/json-binding/src/main/resources/org/glassfish/jersey/jsonb/localization.properties index c7b5a949d5..4eee493a70 100644 --- a/media/json-binding/src/main/resources/org/glassfish/jersey/jsonb/localization.properties +++ b/media/json-binding/src/main/resources/org/glassfish/jersey/jsonb/localization.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2017, 2018 Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2017, 2019 Oracle and/or its affiliates. All rights reserved. # # This program and the accompanying materials are made available under the # terms of the Eclipse Public License v. 2.0, which is available at @@ -16,3 +16,4 @@ error.jsonb.serialization=Error writing JSON-B serialized object. error.jsonb.deserialization=Error deserializing object from entity stream. +error.jsonb.emptystream=JSON-B cannot parse empty input stream. From e0fa5ca6baa779367e11bfc1fb2e0affff03a87d Mon Sep 17 00:00:00 2001 From: Maxim Nesen Date: Mon, 30 Sep 2019 20:26:44 +0200 Subject: [PATCH 4/5] Test with MultiValueHashMap Signed-off-by: Maxim Nesen --- .../internal/JsonBindingProviderTest.java | 95 +------------------ 1 file changed, 2 insertions(+), 93 deletions(-) diff --git a/media/json-binding/src/test/java/org/glassfish/jersey/jsonb/internal/JsonBindingProviderTest.java b/media/json-binding/src/test/java/org/glassfish/jersey/jsonb/internal/JsonBindingProviderTest.java index dbc202aabe..1a8cf00bc1 100644 --- a/media/json-binding/src/test/java/org/glassfish/jersey/jsonb/internal/JsonBindingProviderTest.java +++ b/media/json-binding/src/test/java/org/glassfish/jersey/jsonb/internal/JsonBindingProviderTest.java @@ -29,6 +29,7 @@ import java.util.Set; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedHashMap; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.NoContentException; import javax.ws.rs.ext.ContextResolver; @@ -54,7 +55,7 @@ public final void shouldThrowNoContentException() throws IOException { // when mbr.readFrom(Foo.class, Foo.class, new Annotation[0], APPLICATION_JSON_TYPE, - new EmptyMultivaluedMap(), new ByteArrayInputStream(new byte[0])); + new MultivaluedHashMap<>(), new ByteArrayInputStream(new byte[0])); // then // should throw NoContentException @@ -90,96 +91,4 @@ public final ContextResolver getContextResolver(final Class contextTyp } - private static final class EmptyMultivaluedMap implements MultivaluedMap { - - @Override - public final int size() { - return 0; - } - - @Override - public final boolean isEmpty() { - return true; - } - - @Override - public final boolean containsKey(final Object key) { - return false; - } - - @Override - public final boolean containsValue(final Object value) { - return false; - } - - @Override - public final List get(final Object key) { - return null; - } - - @Override - public final List put(final K key, final List value) { - return null; - } - - @Override - public final List remove(final Object key) { - return null; - } - - @Override - public final void putAll(final Map> m) { - } - - @Override - public final void clear() { - } - - @Override - public final Set keySet() { - return Collections.emptySet(); - } - - @Override - public final Collection> values() { - return Collections.emptySet(); - } - - @Override - public final Set>> entrySet() { - return Collections.emptySet(); - } - - @Override - public final void putSingle(final K key, final V value) { - } - - @Override - public final void add(final K key, final V value) { - } - - @Override - public final V getFirst(final K key) { - return null; - } - - @Override - public final void addAll(final K key, final V... newValues) { - } - - @Override - public final void addAll(final K key, final List valueList) { - } - - @Override - public final void addFirst(final K key, final V value) { - } - - @Override - public final boolean equalsIgnoreValueOrder(final MultivaluedMap otherMap) { - return false; - } - - } - } From 1ef5ec11489297ed0ea2b2c01fb80df3128c6ccb Mon Sep 17 00:00:00 2001 From: Maxim Nesen Date: Tue, 1 Oct 2019 19:57:55 +0200 Subject: [PATCH 5/5] use EntityInputStream to check if stream is empty Signed-off-by: Maxim Nesen --- .../jsonb/internal/JsonBindingProvider.java | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/media/json-binding/src/main/java/org/glassfish/jersey/jsonb/internal/JsonBindingProvider.java b/media/json-binding/src/main/java/org/glassfish/jersey/jsonb/internal/JsonBindingProvider.java index c8a1c7eb19..3ac8031e87 100644 --- a/media/json-binding/src/main/java/org/glassfish/jersey/jsonb/internal/JsonBindingProvider.java +++ b/media/json-binding/src/main/java/org/glassfish/jersey/jsonb/internal/JsonBindingProvider.java @@ -19,7 +19,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.PushbackInputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Type; @@ -41,6 +40,7 @@ import org.glassfish.jersey.jsonb.LocalizationMessages; import org.glassfish.jersey.message.internal.AbstractMessageReaderWriterProvider; +import org.glassfish.jersey.message.internal.EntityInputStream; /** * Entity provider (reader and writer) for JSONB. @@ -72,20 +72,10 @@ public Object readFrom(Class type, Type genericType, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { - if (entityStream.markSupported()) { - entityStream.mark(1); - if (entityStream.read() == -1) { - throw new NoContentException(LocalizationMessages.ERROR_JSONB_EMPTYSTREAM()); - } - entityStream.reset(); - } else { - final PushbackInputStream buffer = new PushbackInputStream(entityStream); - final int firstByte = buffer.read(); - if (firstByte == -1) { - throw new NoContentException(LocalizationMessages.ERROR_JSONB_EMPTYSTREAM()); - } - buffer.unread(firstByte); - entityStream = buffer; + final EntityInputStream entityInputStream = new EntityInputStream(entityStream); + entityStream = entityInputStream; + if (entityInputStream.isEmpty()) { + throw new NoContentException(LocalizationMessages.ERROR_JSONB_EMPTYSTREAM()); } Jsonb jsonb = getJsonb(type);