You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@johnzon.apache.org by rm...@apache.org on 2019/09/29 14:57:38 UTC
[johnzon] branch master updated: JOHNZON-281 ensure
NoContentException can be thrown when an empty incoming stream arrives in
JsonbJaxrsProvider and JsrProvider
This is an automated email from the ASF dual-hosted git repository.
rmannibucau pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/johnzon.git
The following commit(s) were added to refs/heads/master by this push:
new a3b6f44 JOHNZON-281 ensure NoContentException can be thrown when an empty incoming stream arrives in JsonbJaxrsProvider and JsrProvider
a3b6f44 is described below
commit a3b6f4460196b69f47751471c53ca8085d542ee3
Author: Romain Manni-Bucau <rm...@apache.org>
AuthorDate: Sun Sep 29 16:57:27 2019 +0200
JOHNZON-281 ensure NoContentException can be thrown when an empty incoming stream arrives in JsonbJaxrsProvider and JsrProvider
---
.../org/apache/johnzon/jaxrs/DelegateProvider.java | 20 ++++-
.../org/apache/johnzon/jaxrs/JohnzonProvider.java | 4 +
.../java/org/apache/johnzon/jaxrs/JsrProvider.java | 19 +++++
.../jaxrs/NoContentExceptionHandlerReader.java | 63 +++++++++++++++
.../johnzon/jaxrs/WildcardJohnzonProvider.java | 4 +
.../apache/johnzon/jaxrs/WildcardJsrProvider.java | 4 +
.../jaxrs/jsonb/jaxrs/JsonbJaxrsProvider.java | 65 ++++++++++++++--
.../jaxrs/jsonb/jaxrs/JsonbJaxrsProviderTest.java | 91 ++++++++++++++++++++++
.../apache/johnzon/jsonb/jaxrs/JsonbJaxRsTest.java | 2 +-
src/site/markdown/index.md | 9 +++
10 files changed, 274 insertions(+), 7 deletions(-)
diff --git a/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/DelegateProvider.java b/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/DelegateProvider.java
index aee0790..21a2b0e 100644
--- a/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/DelegateProvider.java
+++ b/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/DelegateProvider.java
@@ -18,6 +18,8 @@
*/
package org.apache.johnzon.jaxrs;
+import static java.util.Optional.ofNullable;
+
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
@@ -33,7 +35,8 @@ public abstract class DelegateProvider<T> implements MessageBodyWriter<T>, Messa
private final MessageBodyWriter<T> writer;
protected DelegateProvider(final MessageBodyReader<T> reader, final MessageBodyWriter<T> writer) {
- this.reader = reader;
+ this.reader = shouldThrowNoContentExceptionOnEmptyStreams() && isJaxRs2() ?
+ new NoContentExceptionHandlerReader<>(reader) : reader;
this.writer = writer;
}
@@ -70,4 +73,19 @@ public abstract class DelegateProvider<T> implements MessageBodyWriter<T>, Messa
final OutputStream entityStream) throws IOException {
writer.writeTo(t, rawType, genericType, annotations, mediaType, httpHeaders, entityStream);
}
+
+ protected boolean shouldThrowNoContentExceptionOnEmptyStreams() {
+ return false;
+ }
+
+ private static boolean isJaxRs2() {
+ try {
+ ofNullable(Thread.currentThread().getContextClassLoader())
+ .orElseGet(ClassLoader::getSystemClassLoader)
+ .loadClass("javax.ws.rs.core.Feature");
+ return true;
+ } catch (final NoClassDefFoundError | ClassNotFoundException e) {
+ return false;
+ }
+ }
}
diff --git a/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/JohnzonProvider.java b/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/JohnzonProvider.java
index 9847c4d..b99e42b 100644
--- a/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/JohnzonProvider.java
+++ b/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/JohnzonProvider.java
@@ -37,4 +37,8 @@ public class JohnzonProvider<T> extends DelegateProvider<T> {
public JohnzonProvider() {
this(new MapperBuilder().setDoCloseOnStreams(false).build(), null);
}
+
+ protected boolean shouldThrowNoContentExceptionOnEmptyStreams() {
+ return Boolean.getBoolean("johnzon.jaxrs.johnzon.throwNoContentExceptionOnEmptyStreams");
+ }
}
diff --git a/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/JsrProvider.java b/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/JsrProvider.java
index 06ec86d..872dfe9 100644
--- a/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/JsrProvider.java
+++ b/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/JsrProvider.java
@@ -18,9 +18,16 @@
*/
package org.apache.johnzon.jaxrs;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+
import javax.json.JsonStructure;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.Provider;
@Provider
@@ -30,4 +37,16 @@ public class JsrProvider extends DelegateProvider<JsonStructure> {
public JsrProvider() {
super(new JsrMessageBodyReader(), new JsrMessageBodyWriter());
}
+
+ @Override
+ public JsonStructure readFrom(final Class<JsonStructure> rawType, final Type genericType,
+ final Annotation[] annotations, final MediaType mediaType,
+ final MultivaluedMap<String, String> httpHeaders,
+ final InputStream entityStream) throws IOException {
+ return super.readFrom(rawType, genericType, annotations, mediaType, httpHeaders, entityStream);
+ }
+
+ protected boolean shouldThrowNoContentExceptionOnEmptyStreams() {
+ return Boolean.getBoolean("johnzon.jaxrs.jsr.throwNoContentExceptionOnEmptyStreams");
+ }
}
diff --git a/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/NoContentExceptionHandlerReader.java b/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/NoContentExceptionHandlerReader.java
new file mode 100644
index 0000000..acd496d
--- /dev/null
+++ b/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/NoContentExceptionHandlerReader.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.johnzon.jaxrs;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.NoContentException;
+import javax.ws.rs.ext.MessageBodyReader;
+
+public class NoContentExceptionHandlerReader<T> implements MessageBodyReader<T> {
+ private final MessageBodyReader<T> delegate;
+
+ public NoContentExceptionHandlerReader(final MessageBodyReader<T> delegate) {
+ this.delegate = delegate;
+ }
+
+ public MessageBodyReader<T> getDelegate() {
+ return delegate;
+ }
+
+ @Override
+ public boolean isReadable(final Class<?> type, final Type genericType, final Annotation[] annotations, final MediaType mediaType) {
+ return delegate.isReadable(type, genericType, annotations, mediaType);
+ }
+
+ @Override
+ public T readFrom(final Class<T> type, final Type genericType, final Annotation[] annotations,
+ final MediaType mediaType, final MultivaluedMap<String, String> httpHeaders,
+ final InputStream entityStream) throws IOException, WebApplicationException {
+ try {
+ return delegate.readFrom(type, genericType, annotations, mediaType, httpHeaders, entityStream);
+ } catch (final IllegalStateException ise) {
+ if (ise.getClass().getName()
+ .equals("org.apache.johnzon.core.JsonReaderImpl$NothingToRead")) {
+ // spec enables to return an empty java object but it does not mean anything in JSON context so just fail
+ throw new NoContentException(ise);
+ }
+ throw ise;
+ }
+ }
+}
diff --git a/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/WildcardJohnzonProvider.java b/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/WildcardJohnzonProvider.java
index 275c0f1..585622d 100644
--- a/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/WildcardJohnzonProvider.java
+++ b/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/WildcardJohnzonProvider.java
@@ -45,4 +45,8 @@ public class WildcardJohnzonProvider<T> extends DelegateProvider<T> {
public WildcardJohnzonProvider() {
this(new MapperBuilder().setDoCloseOnStreams(false).build(), null);
}
+
+ protected boolean shouldThrowNoContentExceptionOnEmptyStreams() {
+ return Boolean.getBoolean("johnzon.jaxrs.johnzon.wildcard.throwNoContentExceptionOnEmptyStreams");
+ }
}
diff --git a/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/WildcardJsrProvider.java b/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/WildcardJsrProvider.java
index e424207..657a69e 100644
--- a/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/WildcardJsrProvider.java
+++ b/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/WildcardJsrProvider.java
@@ -37,4 +37,8 @@ public class WildcardJsrProvider extends DelegateProvider<JsonStructure> {
public WildcardJsrProvider() {
super(new JsrMessageBodyReader(), new JsrMessageBodyWriter());
}
+
+ protected boolean shouldThrowNoContentExceptionOnEmptyStreams() {
+ return Boolean.getBoolean("johnzon.jaxrs.jsr.wildcard.throwNoContentExceptionOnEmptyStreams");
+ }
}
diff --git a/johnzon-jsonb/src/main/java/org/apache/johnzon/jaxrs/jsonb/jaxrs/JsonbJaxrsProvider.java b/johnzon-jsonb/src/main/java/org/apache/johnzon/jaxrs/jsonb/jaxrs/JsonbJaxrsProvider.java
index 15e26fc..20d547a 100644
--- a/johnzon-jsonb/src/main/java/org/apache/johnzon/jaxrs/jsonb/jaxrs/JsonbJaxrsProvider.java
+++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jaxrs/jsonb/jaxrs/JsonbJaxrsProvider.java
@@ -18,6 +18,7 @@
*/
package org.apache.johnzon.jaxrs.jsonb.jaxrs;
+import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toMap;
import javax.json.JsonStructure;
@@ -29,6 +30,7 @@ import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.NoContentException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
import javax.ws.rs.ext.MessageBodyReader;
@@ -63,7 +65,9 @@ public class JsonbJaxrsProvider<T> implements MessageBodyWriter<T>, MessageBodyR
protected final Collection<String> ignores;
protected final JsonbConfig config = new JsonbConfig();
protected volatile Function<Class<?>, Jsonb> delegate = null;
+ protected volatile ReadImpl readImpl = null;
private boolean customized;
+ private Boolean throwNoContentExceptionOnEmptyStreams;
@Context
private Providers providers;
@@ -80,6 +84,11 @@ public class JsonbJaxrsProvider<T> implements MessageBodyWriter<T>, MessageBodyR
return ignores != null && ignores.contains(type.getName());
}
+ public void setThrowNoContentExceptionOnEmptyStreams(final boolean throwNoContentExceptionOnEmptyStreams) {
+ this.throwNoContentExceptionOnEmptyStreams = throwNoContentExceptionOnEmptyStreams;
+ // customized = false since it is not a jsonb customization but a MBR one
+ }
+
// config - main containers support the configuration of providers this way
public void setFailOnUnknownProperties(final boolean active) {
config.setProperty("johnzon.fail-on-unknown-properties", active);
@@ -187,8 +196,9 @@ public class JsonbJaxrsProvider<T> implements MessageBodyWriter<T>, MessageBodyR
@Override
public T readFrom(final Class<T> type, final Type genericType, final Annotation[] annotations, final MediaType mediaType,
- final MultivaluedMap<String, String> httpHeaders, final InputStream entityStream) throws WebApplicationException {
- return getJsonb(type).fromJson(entityStream, genericType);
+ final MultivaluedMap<String, String> httpHeaders, final InputStream entityStream) throws WebApplicationException, IOException {
+ final Jsonb jsonb = getJsonb(type);
+ return (T) readImpl.doRead(jsonb, genericType, entityStream);
}
@Override
@@ -205,22 +215,63 @@ public class JsonbJaxrsProvider<T> implements MessageBodyWriter<T>, MessageBodyR
if (delegate == null){
synchronized (this) {
if (delegate == null) {
+ if (throwNoContentExceptionOnEmptyStreams == null) {
+ throwNoContentExceptionOnEmptyStreams = initThrowNoContentExceptionOnEmptyStreams();
+ }
final ContextResolver<Jsonb> contextResolver = providers.getContextResolver(Jsonb.class, MediaType.APPLICATION_JSON_TYPE);
if (contextResolver != null) {
if (customized) {
- Logger.getLogger(JsonbJaxrsProvider.class.getName())
- .warning("Customizations done on the Jsonb instance will be ignored because a ContextResolver<Jsonb> was found");
+ logger().warning("Customizations done on the Jsonb instance will be ignored because a ContextResolver<Jsonb> was found");
+ }
+ if (throwNoContentExceptionOnEmptyStreams) {
+ logger().warning("Using a ContextResolver<Jsonb>, NoContentException will not be thrown for empty streams");
}
delegate = new DynamicInstance(contextResolver); // faster than contextResolver::getContext
} else {
- delegate = new ProvidedInstance(createJsonb()); // don't recreate it
+ // don't recreate it for perfs
+ delegate = new ProvidedInstance(createJsonb());
}
}
+ readImpl = throwNoContentExceptionOnEmptyStreams ?
+ this::doReadWithNoContentException :
+ this::doRead;
}
}
return delegate.apply(type);
}
+ private boolean initThrowNoContentExceptionOnEmptyStreams() {
+ try {
+ ofNullable(Thread.currentThread().getContextClassLoader())
+ .orElseGet(ClassLoader::getSystemClassLoader)
+ .loadClass("javax.ws.rs.core.Feature");
+ return true;
+ } catch (final NoClassDefFoundError | ClassNotFoundException e) {
+ return false;
+ }
+ }
+
+ private Object doRead(final Jsonb jsonb, final Type t, final InputStream stream) {
+ return jsonb.fromJson(stream, t);
+ }
+
+ private Object doReadWithNoContentException(final Jsonb jsonb, final Type t, final InputStream stream) throws NoContentException {
+ try {
+ return doRead(jsonb, t, stream);
+ } catch (final IllegalStateException ise) {
+ if (ise.getClass().getName()
+ .equals("org.apache.johnzon.core.JsonReaderImpl$NothingToRead")) {
+ // spec enables to return an empty java object but it does not mean anything in JSON context so just fail
+ throw new NoContentException(ise);
+ }
+ throw ise;
+ }
+ }
+
+ private Logger logger() {
+ return Logger.getLogger(JsonbJaxrsProvider.class.getName());
+ }
+
@Override
public synchronized void close() throws Exception {
if (AutoCloseable.class.isInstance(delegate)) {
@@ -228,6 +279,10 @@ public class JsonbJaxrsProvider<T> implements MessageBodyWriter<T>, MessageBodyR
}
}
+ private interface ReadImpl {
+ Object doRead(Jsonb jsonb, Type type, InputStream entityStream) throws IOException;
+ }
+
private static final class DynamicInstance implements Function<Class<?>, Jsonb> {
private final ContextResolver<Jsonb> contextResolver;
diff --git a/johnzon-jsonb/src/test/java/org/apache/johnzon/jaxrs/jsonb/jaxrs/JsonbJaxrsProviderTest.java b/johnzon-jsonb/src/test/java/org/apache/johnzon/jaxrs/jsonb/jaxrs/JsonbJaxrsProviderTest.java
new file mode 100644
index 0000000..ae14878
--- /dev/null
+++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jaxrs/jsonb/jaxrs/JsonbJaxrsProviderTest.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.johnzon.jaxrs.jsonb.jaxrs;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.nio.charset.StandardCharsets;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.NoContentException;
+import javax.ws.rs.ext.ContextResolver;
+
+import org.apache.cxf.jaxrs.impl.ProvidersImpl;
+import org.apache.johnzon.core.JsonReaderImpl;
+import org.junit.Test;
+
+public class JsonbJaxrsProviderTest {
+ @Test(expected = NoContentException.class)
+ public void noContentExceptionAuto() throws IOException { // we run on jaxrs 2 in the build
+ readFoo(null, new ByteArrayInputStream(new byte[0]));
+ }
+
+ @Test(expected = NoContentException.class)
+ public void noContentException() throws IOException {
+ readFoo(true, new ByteArrayInputStream(new byte[0]));
+ }
+
+ @Test(expected = JsonReaderImpl.NothingToRead.class)
+ public void noContentExceptionDisabled() throws IOException {
+ readFoo(false, new ByteArrayInputStream(new byte[0]));
+ }
+
+ @Test // just to ensure we didnt break soemthing on read impl
+ public void validTest() throws IOException {
+ final Foo foo = readFoo(null, new ByteArrayInputStream("{\"name\":\"ok\"}".getBytes(StandardCharsets.UTF_8)));
+ assertEquals("ok", foo.name);
+ }
+
+ private Foo readFoo(final Boolean set, final InputStream stream) throws IOException {
+ return new JsonbJaxrsProvider<Foo>() {{
+ if (set != null) {
+ setThrowNoContentExceptionOnEmptyStreams(set);
+ }
+ setProviders(this);
+ }}.readFrom(Foo.class, Foo.class, new Annotation[0],
+ MediaType.APPLICATION_JSON_TYPE, new MultivaluedHashMap<>(),
+ stream);
+ }
+
+ private void setProviders(final JsonbJaxrsProvider<Foo> provider) {
+ try {
+ final Field providers = JsonbJaxrsProvider.class.getDeclaredField("providers");
+ providers.setAccessible(true);
+ providers.set(provider, new ProvidersImpl(null) {
+ @Override
+ public <T> ContextResolver<T> getContextResolver(final Class<T> contextType, final MediaType mediaType) {
+ return null;
+ }
+ });
+ } catch (final Exception e) {
+ fail(e.getMessage());
+ }
+ }
+
+ public static class Foo {
+ public String name;
+ }
+}
diff --git a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/jaxrs/JsonbJaxRsTest.java b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/jaxrs/JsonbJaxRsTest.java
index 91fca7d..890f7ab 100644
--- a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/jaxrs/JsonbJaxRsTest.java
+++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/jaxrs/JsonbJaxRsTest.java
@@ -168,7 +168,7 @@ public class JsonbJaxRsTest {
return null;
}
};
- final List<Johnzon> johnzons = client().path("johnzon/all2").get(new GenericType<List<Johnzon>>(list));
+ final List<Johnzon> johnzons = client().path("johnzon/all2").get(new GenericType<List<Johnzon>>(list) {});
assertEquals(2, johnzons.size());
int i = 1;
for (final Johnzon f : johnzons) {
diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md
index 0170515..91f3f9f 100644
--- a/src/site/markdown/index.md
+++ b/src/site/markdown/index.md
@@ -251,6 +251,8 @@ split makes it easier to mix json and other MediaType in the same resource (like
Tip: ConfigurableJohnzonProvider maps most of MapperBuilder configuration letting you configure it through any IoC including not programming language based formats.
+IMPORTANT: when used with `johnzon-core`, `NoContentException` is not thrown in case of an empty incoming input stream by these providers except `JsrProvider` to limit the breaking changes.
+
### TomEE Configuration
TomEE uses by default Johnzon as JAX-RS provider for versions 7.x. If you want however to customize it you need to follow this procedure:
@@ -315,6 +317,13 @@ JsonbConfig specific properties:
TIP: more in JohnzonBuilder class.
+A JAX-RS provider based on JSON-B is provided in the module as well. It is `org.apache.johnzon.jaxrs.jsonb.jaxrs.JsonbJaxrsProvider`.
+
+IMPORTANT: in JAX-RS 1.0 the provider can throw any exception he wants for an empty incoming stream on reader side. This had been broken in JAX-RS 2.x where it must throw a `javax.ws.rs.core.NoContentException`.
+To ensure you can pick the implementation you can and limit the breaking changes, you can set ̀throwNoContentExceptionOnEmptyStreams` on the provider to switch between both behaviors.
+Default will be picked from the current available API. Finally, this behavior only works with `johnzon-core`.
+
+
#### Integration with `JsonValue`
You can use some optimization to map a `JsonObject` to a POJO using Johnzon `JsonValueReader` and `JsonValueWriter`: