Merge branch 'master' of https://gitbox.apache.org/repos/asf/johnzon
diff --git a/.travis.yml b/.travis.yml
index e0b4fe6..eae0453 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,7 +2,10 @@
sudo: false
jdk:
- - oraclejdk8
-before_install: mvn clean install -DskipTests=true
-after_success:
- - mvn clean cobertura:cobertura coveralls:report
+ - openjdk8
+ - openjdk11
+
+env:
+ global:
+ - MAVEN_OPTS="-Dmaven.artifact.threads=128"
+
diff --git a/NOTICE b/NOTICE
index cb43ecb..e1cbd0e 100644
--- a/NOTICE
+++ b/NOTICE
@@ -1,5 +1,5 @@
Apache Johnzon
-Copyright 2014-2018 The Apache Software Foundation
+Copyright 2014-2019 The Apache Software Foundation
This product includes software developed at
The Apache Software Foundation (http://www.apache.org/).
diff --git a/johnzon-core/pom.xml b/johnzon-core/pom.xml
index f589f8f..4bc1a1b 100644
--- a/johnzon-core/pom.xml
+++ b/johnzon-core/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>johnzon</artifactId>
<groupId>org.apache.johnzon</groupId>
- <version>1.2.1-SNAPSHOT</version>
+ <version>1.2.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/AbstractJsonFactory.java b/johnzon-core/src/main/java/org/apache/johnzon/core/AbstractJsonFactory.java
index 40194bd..ded93e3 100644
--- a/johnzon-core/src/main/java/org/apache/johnzon/core/AbstractJsonFactory.java
+++ b/johnzon-core/src/main/java/org/apache/johnzon/core/AbstractJsonFactory.java
@@ -78,4 +78,14 @@
return Boolean.parseBoolean(boolValue.toString());
}
+ protected String getString(final String key, final String defaultValue) {
+ final Object value = internalConfig.get(key);
+ if (value == null) {
+ return defaultValue;
+ } else if (String.class.isInstance(value)) {
+ return String.class.cast(value);
+ }
+ return value.toString();
+ }
+
}
diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonArrayBuilderImpl.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonArrayBuilderImpl.java
index af2aa25..5ea8c65 100644
--- a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonArrayBuilderImpl.java
+++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonArrayBuilderImpl.java
@@ -298,7 +298,7 @@
addValue(builder.build());
return this;
}
-
+
private void setValue(int idx, JsonValue value) {
if (value == null || tmpList == null) {
throw npe();
@@ -333,7 +333,7 @@
@Override
public JsonArray build() {
if(tmpList == null) {
- return new JsonArrayImpl(Collections.emptyList(), bufferProvider);
+ return JsonValue.EMPTY_JSON_ARRAY;
}
return new JsonArrayImpl(Collections.unmodifiableList(tmpList), bufferProvider);
}
diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonObjectBuilderImpl.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonObjectBuilderImpl.java
index de13a0a..fafcc74 100644
--- a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonObjectBuilderImpl.java
+++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonObjectBuilderImpl.java
@@ -176,16 +176,16 @@
if(name == null || value == null) {
throw new NullPointerException("name or value/builder must not be null");
}
-
+
attributeMap.put(name, value);
}
-
+
@Override
public JsonObject build() {
-
+
if(attributeMap == null || attributeMap.isEmpty()) {
- return new JsonObjectImpl(Collections.EMPTY_MAP, bufferProvider);
+ return JsonValue.EMPTY_JSON_OBJECT;
} else {
Map<String, JsonValue> dump = (Collections.unmodifiableMap(attributeMap));
return new JsonObjectImpl(dump, bufferProvider);
diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonParserFactoryImpl.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonParserFactoryImpl.java
index 437a2ca..a1651df 100644
--- a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonParserFactoryImpl.java
+++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonParserFactoryImpl.java
@@ -30,6 +30,7 @@
import java.util.Map;
import static java.util.Arrays.asList;
+import static java.util.Optional.ofNullable;
public class JsonParserFactoryImpl extends AbstractJsonFactory implements JsonParserFactory {
public static final String MAX_STRING_LENGTH = "org.apache.johnzon.max-string-length";
@@ -40,10 +41,11 @@
public static final int DEFAULT_BUFFER_LENGTH = Integer.getInteger(BUFFER_LENGTH, 64 * 1024); //64k
public static final String SUPPORTS_COMMENTS = "org.apache.johnzon.supports-comments";
+ public static final String ENCODING = "org.apache.johnzon.encoding";
public static final boolean DEFAULT_SUPPORTS_COMMENT = Boolean.getBoolean(SUPPORTS_COMMENTS); //default is false;
static final Collection<String> SUPPORTED_CONFIG_KEYS = asList(
- BUFFER_STRATEGY, MAX_STRING_LENGTH, BUFFER_LENGTH, SUPPORTS_COMMENTS, AUTO_ADJUST_STRING_BUFFER
+ BUFFER_STRATEGY, MAX_STRING_LENGTH, BUFFER_LENGTH, SUPPORTS_COMMENTS, AUTO_ADJUST_STRING_BUFFER, ENCODING
);
private final int maxSize;
@@ -51,6 +53,7 @@
private final BufferStrategy.BufferProvider<char[]> valueBufferProvider;
private final boolean supportsComments;
private final boolean autoAdjustBuffers;
+ private final Charset defaultEncoding;
JsonParserFactoryImpl(final Map<String, ?> config) {
super(config, SUPPORTED_CONFIG_KEYS, null);
@@ -65,9 +68,13 @@
this.valueBufferProvider = getBufferProvider().newCharProvider(maxSize);
this.supportsComments = getBool(SUPPORTS_COMMENTS, DEFAULT_SUPPORTS_COMMENT);
this.autoAdjustBuffers = getBool(AUTO_ADJUST_STRING_BUFFER, true);
+ this.defaultEncoding = ofNullable(getString(ENCODING, null)).map(Charset::forName).orElse(null);
}
private JsonStreamParserImpl getDefaultJsonParserImpl(final InputStream in) {
+ if (defaultEncoding != null) {
+ return getDefaultJsonParserImpl(in, defaultEncoding);
+ }
if (supportsComments) {
return new CommentsJsonStreamParserImpl(in, maxSize, bufferProvider, valueBufferProvider, autoAdjustBuffers);
}
diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonReaderImpl.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonReaderImpl.java
index 71bd0e1..24de161 100644
--- a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonReaderImpl.java
+++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonReaderImpl.java
@@ -72,7 +72,7 @@
checkClosed();
if (!parser.hasNext()) {
- throw new IllegalStateException("Nothing to read");
+ throw new NothingToRead();
}
@@ -282,4 +282,10 @@
}
}
+
+ public static class NothingToRead extends IllegalStateException {
+ public NothingToRead() {
+ super("Nothing to read");
+ }
+ }
}
diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonStreamParserImpl.java b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonStreamParserImpl.java
index fd05752..bf4a2c0 100644
--- a/johnzon-core/src/main/java/org/apache/johnzon/core/JsonStreamParserImpl.java
+++ b/johnzon-core/src/main/java/org/apache/johnzon/core/JsonStreamParserImpl.java
@@ -21,8 +21,10 @@
import javax.json.JsonException;
import javax.json.stream.JsonLocation;
import javax.json.stream.JsonParsingException;
+
import java.io.IOException;
import java.io.InputStream;
+import java.io.InputStreamReader;
import java.io.Reader;
import java.math.BigDecimal;
import java.nio.charset.Charset;
@@ -149,8 +151,10 @@
if (reader != null) {
this.in = reader;
- } else {
- this.in = new RFC4627AwareInputStreamReader(inputStream, encoding);
+ } else if (encoding != null) { // always respect it
+ this.in = new InputStreamReader(inputStream, encoding);
+ } else { // should we log the usage is unexpected? (for perf we want to avoid the pushbackinputstream)
+ this.in = new RFC4627AwareInputStreamReader(inputStream);
}
}
@@ -207,7 +211,7 @@
if (previousEvent != END_ARRAY && previousEvent != END_OBJECT &&
previousEvent != VALUE_STRING && previousEvent != VALUE_FALSE && previousEvent != VALUE_TRUE &&
previousEvent != VALUE_NULL && previousEvent != VALUE_NUMBER) {
- if (bufferPos == Integer.MIN_VALUE) { // check we don't have an empty string to parse
+ if (bufferPos < 0) { // check we don't have an empty string to parse
final char c = readNextChar();
bufferPos--;
return c != EOF;
diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/RFC4627AwareInputStreamReader.java b/johnzon-core/src/main/java/org/apache/johnzon/core/RFC4627AwareInputStreamReader.java
index 0fc2784..c3d0c37 100644
--- a/johnzon-core/src/main/java/org/apache/johnzon/core/RFC4627AwareInputStreamReader.java
+++ b/johnzon-core/src/main/java/org/apache/johnzon/core/RFC4627AwareInputStreamReader.java
@@ -29,15 +29,12 @@
final class RFC4627AwareInputStreamReader extends InputStreamReader {
- /**
- * @param preferredCharset the Charset to use if no BOM is used. If {@code null} use UTF-8
- */
- RFC4627AwareInputStreamReader(final InputStream in, Charset preferredCharset) {
- this(new PushbackInputStream(in,4), preferredCharset);
+ RFC4627AwareInputStreamReader(final InputStream in) {
+ this(new PushbackInputStream(in,4));
}
- private RFC4627AwareInputStreamReader(final PushbackInputStream in, Charset preferredCharset) {
- super(in, getCharset(in, preferredCharset).newDecoder());
+ private RFC4627AwareInputStreamReader(final PushbackInputStream in) {
+ super(in, getCharset(in).newDecoder());
}
/**
@@ -86,8 +83,8 @@
*/
- private static Charset getCharset(final PushbackInputStream inputStream, Charset preferredCharset) {
- Charset charset = preferredCharset != null ? preferredCharset : StandardCharsets.UTF_8;
+ private static Charset getCharset(final PushbackInputStream inputStream) {
+ Charset charset = StandardCharsets.UTF_8;
int bomLength=0;
try {
final byte[] utfBytes = readAllBytes(inputStream);
diff --git a/johnzon-core/src/test/java/org/apache/johnzon/core/JsonReaderImplTest.java b/johnzon-core/src/test/java/org/apache/johnzon/core/JsonReaderImplTest.java
index e41cdbe..becdf5f 100644
--- a/johnzon-core/src/test/java/org/apache/johnzon/core/JsonReaderImplTest.java
+++ b/johnzon-core/src/test/java/org/apache/johnzon/core/JsonReaderImplTest.java
@@ -18,6 +18,7 @@
*/
package org.apache.johnzon.core;
+import static java.util.Collections.emptyMap;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -425,6 +426,13 @@
reader.close();
}
+ @Test(expected = JsonReaderImpl.NothingToRead.class)
+ public void emptyStream() {
+ Json.createReaderFactory(emptyMap())
+ .createReader(new ByteArrayInputStream(new byte[0]), utf8Charset)
+ .readObject();
+ }
+
@Test(expected = IllegalArgumentException.class)
public void emptyZeroCharBuffersize() {
final JsonReader reader = Json.createReaderFactory(new HashMap<String, Object>() {
diff --git a/johnzon-distribution/pom.xml b/johnzon-distribution/pom.xml
index eaf1f8c..0e4287f 100644
--- a/johnzon-distribution/pom.xml
+++ b/johnzon-distribution/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>johnzon</artifactId>
<groupId>org.apache.johnzon</groupId>
- <version>1.2.1-SNAPSHOT</version>
+ <version>1.2.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/johnzon-jaxrs/pom.xml b/johnzon-jaxrs/pom.xml
index d91a067..c530446 100644
--- a/johnzon-jaxrs/pom.xml
+++ b/johnzon-jaxrs/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>johnzon</artifactId>
<groupId>org.apache.johnzon</groupId>
- <version>1.2.1-SNAPSHOT</version>
+ <version>1.2.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/ConfigurableJohnzonProvider.java b/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/ConfigurableJohnzonProvider.java
index 3ab6682..9b853db 100644
--- a/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/ConfigurableJohnzonProvider.java
+++ b/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/ConfigurableJohnzonProvider.java
@@ -41,14 +41,13 @@
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
-import javax.ws.rs.ext.Provider;
import org.apache.johnzon.mapper.MapperBuilder;
import org.apache.johnzon.mapper.SerializeValueFilter;
import org.apache.johnzon.mapper.access.AccessMode;
import org.apache.johnzon.mapper.access.BaseAccessMode;
-@Provider
+// @Provider // don't let it be scanned, it would conflict with JohnzonProvider
@Produces("application/json")
@Consumes("application/json")
public class ConfigurableJohnzonProvider<T> implements MessageBodyWriter<T>, MessageBodyReader<T> {
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 @@
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 @@
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/JohnzonMessageBodyReader.java b/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/JohnzonMessageBodyReader.java
index 42fcbf0..b65349d 100644
--- a/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/JohnzonMessageBodyReader.java
+++ b/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/JohnzonMessageBodyReader.java
@@ -27,7 +27,6 @@
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.MessageBodyReader;
-import javax.ws.rs.ext.Provider;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
@@ -36,7 +35,7 @@
import java.lang.reflect.Type;
import java.util.Collection;
-@Provider
+// @Provider // don't let it be scanned, it would conflict with JohnzonProvider
@Consumes({
"application/json", "*/json",
"*/*+json", "*/x-json",
diff --git a/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/JohnzonMessageBodyWriter.java b/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/JohnzonMessageBodyWriter.java
index 6a3e163..9c7b1ac 100644
--- a/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/JohnzonMessageBodyWriter.java
+++ b/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/JohnzonMessageBodyWriter.java
@@ -28,7 +28,6 @@
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
import javax.ws.rs.ext.MessageBodyWriter;
-import javax.ws.rs.ext.Provider;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -37,7 +36,7 @@
import java.lang.reflect.Type;
import java.util.Collection;
-@Provider
+// @Provider // don't let it be scanned, it would conflict with JohnzonProvider
@Produces({
"application/json", "*/json",
"*/*+json", "*/x-json",
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 b1e137f..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
@@ -31,10 +31,14 @@
@Consumes("application/json")
public class JohnzonProvider<T> extends DelegateProvider<T> {
public JohnzonProvider(final Mapper mapper, final Collection<String> ignores) {
- super(new JohnzonMessageBodyReader<T>(mapper, ignores), new JohnzonMessageBodyWriter<T>(mapper, ignores));
+ super(new JohnzonMessageBodyReader<>(mapper, ignores), new JohnzonMessageBodyWriter<>(mapper, ignores));
}
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/JsrMessageBodyReader.java b/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/JsrMessageBodyReader.java
index 7a48bd4..69b0825 100644
--- a/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/JsrMessageBodyReader.java
+++ b/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/JsrMessageBodyReader.java
@@ -27,7 +27,6 @@
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 java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
@@ -36,7 +35,7 @@
import static org.apache.johnzon.mapper.internal.Streams.noClose;
-@Provider
+// @Provider // don't let it be scanned, it would conflict with JsrProvider
@Consumes({
"application/json", "*/json",
"*/*+json", "*/x-json",
diff --git a/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/JsrMessageBodyWriter.java b/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/JsrMessageBodyWriter.java
index 6d23c3b..b6163c2 100644
--- a/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/JsrMessageBodyWriter.java
+++ b/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/JsrMessageBodyWriter.java
@@ -27,7 +27,6 @@
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;
-import javax.ws.rs.ext.Provider;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
@@ -36,7 +35,7 @@
import static org.apache.johnzon.mapper.internal.Streams.noClose;
-@Provider
+// @Provider // don't let it be scanned, it would conflict with JsrProvider
@Produces({
"application/json", "*/json",
"*/*+json", "*/x-json",
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 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/WildcardConfigurableJohnzonProvider.java b/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/WildcardConfigurableJohnzonProvider.java
index aa71150..5fc52fa 100644
--- a/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/WildcardConfigurableJohnzonProvider.java
+++ b/johnzon-jaxrs/src/main/java/org/apache/johnzon/jaxrs/WildcardConfigurableJohnzonProvider.java
@@ -31,7 +31,6 @@
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
-import javax.ws.rs.ext.Provider;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -44,7 +43,7 @@
import static java.util.Arrays.asList;
-@Provider
+// @Provider // don't let it be scanned, it would conflict with WildcardJohnzonProvider
@Produces({
"*/json",
"*/*+json", "*/x-json",
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 c573fff..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
@@ -26,7 +26,7 @@
import javax.ws.rs.ext.Provider;
import java.util.Collection;
-@Provider
+@Provider // this is scanned cause does not overlap with JohnzonProvider in terms of mime types
@Produces({
"*/json",
"*/*+json", "*/x-json",
@@ -45,4 +45,8 @@
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 66d3791..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
@@ -21,9 +21,8 @@
import javax.json.JsonStructure;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
-import javax.ws.rs.ext.Provider;
-@Provider
+// @Provider // don't let it be scanned, it would conflict with JsrProvider
@Produces({
"*/json",
"*/*+json", "*/x-json",
@@ -38,4 +37,8 @@
public WildcardJsrProvider() {
super(new JsrMessageBodyReader(), new JsrMessageBodyWriter());
}
+
+ protected boolean shouldThrowNoContentExceptionOnEmptyStreams() {
+ return Boolean.getBoolean("johnzon.jaxrs.jsr.wildcard.throwNoContentExceptionOnEmptyStreams");
+ }
}
diff --git a/johnzon-json-extras/pom.xml b/johnzon-json-extras/pom.xml
index 92f6c3f..74ca0d2 100644
--- a/johnzon-json-extras/pom.xml
+++ b/johnzon-json-extras/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>johnzon</artifactId>
<groupId>org.apache.johnzon</groupId>
- <version>1.2.1-SNAPSHOT</version>
+ <version>1.2.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/johnzon-jsonb/pom.xml b/johnzon-jsonb/pom.xml
index 0a478bb..13b0c85 100644
--- a/johnzon-jsonb/pom.xml
+++ b/johnzon-jsonb/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>johnzon</artifactId>
<groupId>org.apache.johnzon</groupId>
- <version>1.2.1-SNAPSHOT</version>
+ <version>1.2.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
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 30bf771..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.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 @@
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 @@
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,13 +196,14 @@
@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 {
- 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
public void writeTo(final T t, final Class<?> type, final Type genericType, final Annotation[] annotations, final MediaType mediaType,
- final MultivaluedMap<String, Object> httpHeaders, final OutputStream entityStream) throws IOException, WebApplicationException {
+ final MultivaluedMap<String, Object> httpHeaders, final OutputStream entityStream) throws WebApplicationException {
getJsonb(type).toJson(t, entityStream);
}
@@ -205,22 +215,63 @@
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 @@
}
}
+ 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/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
index 89f85a5..4d19dff 100644
--- a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
+++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
@@ -868,8 +868,10 @@
ReaderConverters(final DecoratedType annotationHolder) {
final boolean numberType = isNumberType(annotationHolder.getType());
final boolean dateType = isDateType(annotationHolder.getType());
+ final boolean hasRawType = hasRawType(annotationHolder.getType());
final JsonbTypeDeserializer deserializer = annotationHolder.getAnnotation(JsonbTypeDeserializer.class);
final JsonbTypeAdapter adapter = annotationHolder.getAnnotation(JsonbTypeAdapter.class);
+ final JsonbTypeAdapter typeAdapter = hasRawType ? getRawType(annotationHolder.getType()).getDeclaredAnnotation(JsonbTypeAdapter.class) : null;
JsonbDateFormat dateFormat = dateType ? annotationHolder.getAnnotation(JsonbDateFormat.class) : null;
JsonbNumberFormat numberFormat = numberType ? annotationHolder.getAnnotation(JsonbNumberFormat.class) : null;
final JohnzonConverter johnzonConverter = annotationHolder.getAnnotation(JohnzonConverter.class);
@@ -881,9 +883,9 @@
numberFormat = annotationHolder.getClassOrPackageAnnotation(JsonbNumberFormat.class);
}
- converter = adapter == null && dateFormat == null && numberFormat == null && johnzonConverter == null ?
+ converter = adapter == null && typeAdapter == null && dateFormat == null && numberFormat == null && johnzonConverter == null ?
defaultConverters.get(new AdapterKey(annotationHolder.getType(), String.class)) :
- toConverter(types, annotationHolder.getType(), adapter, dateFormat, numberFormat);
+ toConverter(types, annotationHolder.getType(), adapter != null ? adapter : typeAdapter, dateFormat, numberFormat);
if (deserializer != null) {
final Class<? extends JsonbDeserializer> value = deserializer.value();
@@ -966,12 +968,14 @@
WriterConverters(final DecoratedType reader, final Types types) {
final boolean numberType = isNumberType(reader.getType());
final boolean dateType = isDateType(reader.getType());
+ final boolean hasRawType = hasRawType(reader.getType());
final JsonbTypeSerializer serializer = reader.getAnnotation(JsonbTypeSerializer.class);
final JsonbTypeAdapter adapter = reader.getAnnotation(JsonbTypeAdapter.class);
+ final JsonbTypeAdapter typeAdapter = hasRawType ? getRawType(reader.getType()).getDeclaredAnnotation(JsonbTypeAdapter.class) : null;
JsonbDateFormat dateFormat = dateType ? reader.getAnnotation(JsonbDateFormat.class) : null;
JsonbNumberFormat numberFormat = numberType ? reader.getAnnotation(JsonbNumberFormat.class) : null;
final JohnzonConverter johnzonConverter = reader.getAnnotation(JohnzonConverter.class);
- validateAnnotations(reader, adapter, dateFormat, numberFormat, johnzonConverter);
+ validateAnnotations(reader, adapter != null ? adapter : typeAdapter, dateFormat, numberFormat, johnzonConverter);
if (dateFormat == null && isDateType(reader.getType())) {
dateFormat = reader.getClassOrPackageAnnotation(JsonbDateFormat.class);
}
@@ -979,9 +983,9 @@
numberFormat = reader.getClassOrPackageAnnotation(JsonbNumberFormat.class);
}
- converter = adapter == null && dateFormat == null && numberFormat == null && johnzonConverter == null ?
+ converter = adapter == null && typeAdapter == null && dateFormat == null && numberFormat == null && johnzonConverter == null ?
defaultConverters.get(new AdapterKey(reader.getType(), String.class)) :
- toConverter(types, reader.getType(), adapter, dateFormat, numberFormat);
+ toConverter(types, reader.getType(), adapter != null ? adapter : typeAdapter, dateFormat, numberFormat);
if (serializer != null) {
final Class<? extends JsonbSerializer> value = serializer.value();
@@ -1033,6 +1037,20 @@
return Number.class.isAssignableFrom(clazz) || clazz.isPrimitive();
}
+ private boolean hasRawType(final Type type) {
+ return Class.class.isInstance(type) ||
+ (ParameterizedType.class.isInstance(type) &&
+ Class.class.isInstance(ParameterizedType.class.cast(type).getRawType()));
+ }
+
+ private Class<?> getRawType(final Type type) { // only intended to be used after hasRawType check
+ if (Class.class.isInstance(type)) {
+ return Class.class.cast(type);
+ }
+ // ParameterizedType + Class raw type
+ return Class.class.cast(ParameterizedType.class.cast(type).getRawType());
+ }
+
private static class ClassDecoratedType implements DecoratedType {
private final Class<?> annotations;
diff --git a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/serializer/JohnzonDeserializationContext.java b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/serializer/JohnzonDeserializationContext.java
index a3b58dd..c531449 100644
--- a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/serializer/JohnzonDeserializationContext.java
+++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/serializer/JohnzonDeserializationContext.java
@@ -63,6 +63,10 @@
parseObject(null, parser, objectBuilder);
return objectBuilder.build();
}
+ case END_OBJECT:
+ return JsonValue.EMPTY_JSON_OBJECT;
+ case END_ARRAY:
+ return JsonValue.EMPTY_JSON_ARRAY;
case START_ARRAY:
final JsonArrayBuilder arrayBuilder = builderFactory.createArrayBuilder();
parseArray(parser, arrayBuilder);
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/AdapterTest.java b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/AdapterTest.java
index 8e12cbd..dca4576 100644
--- a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/AdapterTest.java
+++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/AdapterTest.java
@@ -1,3 +1,4 @@
+package org.apache.johnzon.jsonb;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
@@ -16,7 +17,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.johnzon.jsonb;
+
import org.junit.Test;
@@ -32,7 +33,11 @@
import java.util.List;
import static java.util.Arrays.asList;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.endsWith;
+import static org.hamcrest.CoreMatchers.startsWith;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
public class AdapterTest {
@@ -44,13 +49,32 @@
foo.bar.value = 1;
foo.dummy = new Dummy();
foo.dummy.value = 2L;
+ foo.baz = new Baz();
+ foo.baz.value = "3";
final String toString = jsonb.toJson(foo);
- assertEquals("{\"bar\":\"1\",\"dummy\":\"2\"}", toString);
+ assertThat(toString, startsWith("{"));
+ assertThat(toString, containsString("\"bar\":\"1\""));
+ assertThat(toString, containsString("\"dummy\":\"2\""));
+ assertThat(toString, containsString("\"baz\":\"3\""));
+ assertThat(toString, endsWith("}"));
+ assertEquals("{\"bar\":\"1\",\"dummy\":\"2\",\"baz\":\"3\"}".length(), toString.length());
final Foo read = jsonb.fromJson(toString, Foo.class);
assertEquals(foo.bar.value, read.bar.value);
assertEquals(foo.dummy.value, read.dummy.value);
+ assertEquals(foo.baz.value, read.baz.value);
+ }
+ }
+
+ @Test
+ public void adaptValue() throws Exception {
+ try (final Jsonb jsonb = JsonbBuilder.create()) {
+ final Baz baz = new Baz();
+ baz.value = "test";
+
+ final String toString = jsonb.toJson(baz);
+ assertEquals("\"test\"", toString);
}
}
@@ -144,6 +168,8 @@
@JsonbTypeAdapter(DummyAdapter.class)
public Dummy dummy;
+
+ public Baz baz;
}
public static class Bar2 extends Bar {
@@ -169,6 +195,27 @@
}
}
+ @JsonbTypeAdapter(BazAdapter.class)
+ public static class Baz {
+ public String value;
+ }
+
+ public static class BazAdapter implements JsonbAdapter<Baz, String> {
+
+ @Override
+ public String adaptToJson(Baz obj) throws Exception {
+ return obj.value;
+ }
+
+ @Override
+ public Baz adaptFromJson(String obj) throws Exception {
+ Baz baz = new Baz();
+ baz.value = obj;
+ return baz;
+ }
+
+ }
+
public static class Dummy2 {
public long value;
}
diff --git a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/DefaultPropertyVisibilityStrategyTest.java b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/DefaultPropertyVisibilityStrategyTest.java
new file mode 100644
index 0000000..17eeab2
--- /dev/null
+++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/DefaultPropertyVisibilityStrategyTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.jsonb;
+
+import static org.junit.Assert.assertTrue;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+import javax.json.bind.Jsonb;
+import javax.json.bind.JsonbBuilder;
+import javax.json.bind.annotation.JsonbProperty;
+import javax.json.bind.annotation.JsonbVisibility;
+
+import org.junit.Test;
+
+public class DefaultPropertyVisibilityStrategyTest {
+ @Test // note it is not a valid case since our default impl is internal but it guarantees we dont wrongly impl equals
+ public void subclassing() throws Exception {
+ try (final Jsonb jsonb = JsonbBuilder.create()) {
+ final TheClass theClass = jsonb.fromJson("{\"foo\":true}", TheClass.class);
+ assertTrue(theClass.isFoo());
+ }
+ }
+
+ @Test
+ public void annotated() throws Exception {
+ try (final Jsonb jsonb = JsonbBuilder.create()) {
+ final VisibleCauseAnnotated theClass = jsonb.fromJson("{\"foo\":true}", VisibleCauseAnnotated.class);
+ assertTrue(theClass.isFoo());
+ }
+ }
+
+ public static class MyVisibility extends DefaultPropertyVisibilityStrategy {
+ @Override
+ public boolean isVisible(final Field field) {
+ return true;
+ }
+
+ @Override
+ public boolean isVisible(final Method method) {
+ return true;
+ }
+ }
+
+ @JsonbVisibility(MyVisibility.class)
+ public static final class TheClass {
+ @JsonbProperty
+ private boolean foo;
+
+ public final boolean isFoo() {
+ return foo;
+ }
+ }
+
+ public static final class VisibleCauseAnnotated {
+ @JsonbProperty
+ private boolean foo;
+
+ public final boolean isFoo() {
+ return foo;
+ }
+ }
+}
diff --git a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/GenericAdapterTest.java b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/GenericAdapterTest.java
index 6e96ca6..730e27c 100644
--- a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/GenericAdapterTest.java
+++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/GenericAdapterTest.java
@@ -1,12 +1,10 @@
/**
- * Copyright (C) 2006-2019 Talend Inc. - www.talend.com
- * <p>
* Licensed 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
- * <p>
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- * <p>
+ *
* 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.
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 @@
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/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/serializer/DeserializationContextTest.java b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/serializer/DeserializationContextTest.java
new file mode 100644
index 0000000..1be5e35
--- /dev/null
+++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/serializer/DeserializationContextTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.jsonb.serializer;
+
+import static org.junit.Assert.assertNotNull;
+
+import java.lang.reflect.Type;
+
+import javax.json.bind.Jsonb;
+import javax.json.bind.JsonbBuilder;
+import javax.json.bind.JsonbConfig;
+import javax.json.bind.serializer.DeserializationContext;
+import javax.json.bind.serializer.JsonbDeserializer;
+import javax.json.stream.JsonParser;
+
+import org.junit.Test;
+
+public class DeserializationContextTest {
+
+ /**
+ * Test Case for <a href="https://issues.apache.org/jira/browse/JOHNZON-277">JOHNZON-277</a>
+ *
+ * According to https://github.com/eclipse-ee4j/jsonb-api/blob/master/api/src/main/java/javax/json/bind/serializer/DeserializationContext.java#L34-L35 a
+ * deserialization context must be able to deserialize an object EVEN IN CASE the parser cursor is at {@code START_OBJECT} or {@code START_ARRAY}.
+ *
+ * Quote: "JsonParser cursor have to be at KEY_NAME before START_OBJECT / START_ARRAY, or at START_OBJECT / START_ARRAY 35 * to call this method."
+ *
+ * The latter case, "...or at START_OBJECT..." seems to fail in case the inner JSON to parse is an empty block "{}" (also for empty arrays).
+ */
+ @Test
+ public void issue277() throws Exception {
+ try (final Jsonb jsonb = JsonbBuilder.create()) {
+ // CAN create Bar and Bar[] instance from empty JSON inside of Foo
+ final Foo foo = jsonb.fromJson("{\"bar\":{},\"bars\":[]}", Foo.class);
+ assertNotNull(foo);
+ assertNotNull(foo.bar);
+ assertNotNull(foo.bars);
+ }
+
+ try (final Jsonb jsonb = JsonbBuilder.create(new JsonbConfig().withDeserializers(new CustomDeserializer()))) {
+ // CANNOT create Bar nor Bar[] instance from empty JSON when parser cursor is at START_OBJECT => Bug in Johnzon
+ final Foo foo = jsonb.fromJson("{\"bar\":{},\"bars\":[]}", Foo.class); // throws exception "Unknown structure: END_OBJECT / END_ARRAY"
+ assertNotNull(foo);
+ assertNotNull(foo.bar);
+ assertNotNull(foo.bars);
+ }
+ }
+
+ public static class CustomDeserializer implements JsonbDeserializer<Foo> {
+ @Override
+ public Foo deserialize(final JsonParser parser, final DeserializationContext ctx, final Type rtType) {
+ parser.next(); // now cursor is at START_OBJECT of outer Foo, so let's create empty Foo...
+ final Foo foo = new Foo();
+ parser.next(); // now cursor is at KEY_NAME "bar"
+ parser.next(); // now cursor is at START_OBJECT of inner Bar, so let's fill Foo.bar...
+ foo.bar = ctx.deserialize(Bar.class, parser);
+ parser.next(); // now cursor is at KEY_NAME "bars"
+ parser.next(); // now cursor is at START_ARRAY of inner Bar[], so let's fill Foo.bars...
+ foo.bars = ctx.deserialize(Bar[].class, parser);
+ return foo;
+ }
+ }
+
+ public static class Foo {
+ public Bar bar;
+ public Bar[] bars;
+ }
+
+ public static class Bar {
+ // intentionally left blank
+ }
+
+}
diff --git a/johnzon-jsonschema/pom.xml b/johnzon-jsonschema/pom.xml
index 744708f..6bc83dc 100644
--- a/johnzon-jsonschema/pom.xml
+++ b/johnzon-jsonschema/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>johnzon</artifactId>
<groupId>org.apache.johnzon</groupId>
- <version>1.2.1-SNAPSHOT</version>
+ <version>1.2.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/johnzon-mapper/pom.xml b/johnzon-mapper/pom.xml
index 8c5f75f..ccd086c 100644
--- a/johnzon-mapper/pom.xml
+++ b/johnzon-mapper/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>johnzon</artifactId>
<groupId>org.apache.johnzon</groupId>
- <version>1.2.1-SNAPSHOT</version>
+ <version>1.2.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java
index b05667c..6eadc0e 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java
@@ -22,7 +22,7 @@
import static java.util.Collections.emptyMap;
import static java.util.Locale.ROOT;
-import org.apache.johnzon.core.JsonParserFactoryImpl;
+// import org.apache.johnzon.core.JsonParserFactoryImpl; // don't depend on core in mapper
import org.apache.johnzon.mapper.access.AccessMode;
import org.apache.johnzon.mapper.access.BaseAccessMode;
import org.apache.johnzon.mapper.access.FieldAccessMode;
@@ -158,7 +158,7 @@
}
final Map<String, Object> config = new HashMap<String, Object>();
if (bufferStrategy != null) {
- config.put(JsonParserFactoryImpl.BUFFER_STRATEGY, bufferStrategy);
+ config.put("org.apache.johnzon.buffer-strategy", bufferStrategy);
}
if (pretty) {
config.put(JsonGenerator.PRETTY_PRINTING, true);
@@ -170,17 +170,20 @@
config.remove(JsonGenerator.PRETTY_PRINTING); // doesnt mean anything anymore for reader
if (supportsComments) {
- config.put(JsonParserFactoryImpl.SUPPORTS_COMMENTS, "true");
+ config.put("org.apache.johnzon.supports-comments", "true");
}
if (maxSize > 0) {
- config.put(JsonParserFactoryImpl.MAX_STRING_LENGTH, maxSize);
+ config.put("org.apache.johnzon.max-string-length", maxSize);
}
if (bufferSize > 0) {
- config.put(JsonParserFactoryImpl.BUFFER_LENGTH, bufferSize);
+ config.put("org.apache.johnzon.default-char-buffer", bufferSize);
}
if (autoAdjustStringBuffers) {
config.put("org.apache.johnzon.auto-adjust-buffer", true);
}
+ if (encoding != null) {
+ config.put("org.apache.johnzon.encoding", encoding.name());
+ }
if (readerFactory == null) {
readerFactory = provider.createReaderFactory(config);
}
@@ -436,7 +439,7 @@
}
public MapperBuilder setEncoding(final String encoding) {
- this.encoding = Charset.forName(encoding);
+ this.encoding = encoding == null ? null : Charset.forName(encoding);
return this;
}
diff --git a/johnzon-maven-plugin/.gitattributes b/johnzon-maven-plugin/.gitattributes
new file mode 100644
index 0000000..feb3703
--- /dev/null
+++ b/johnzon-maven-plugin/.gitattributes
@@ -0,0 +1,16 @@
+# Copyright (C) 2019 Markus KARG
+#
+# Licensed 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.
+
+src/test/resources/SomeValue.java text eol=lf
+
diff --git a/johnzon-maven-plugin/pom.xml b/johnzon-maven-plugin/pom.xml
index 8482b6e..66cc760 100644
--- a/johnzon-maven-plugin/pom.xml
+++ b/johnzon-maven-plugin/pom.xml
@@ -23,7 +23,7 @@
<parent>
<artifactId>johnzon</artifactId>
<groupId>org.apache.johnzon</groupId>
- <version>1.2.1-SNAPSHOT</version>
+ <version>1.2.2-SNAPSHOT</version>
</parent>
<artifactId>johnzon-maven-plugin</artifactId>
diff --git a/johnzon-websocket/pom.xml b/johnzon-websocket/pom.xml
index 52c87d5..a58be30 100644
--- a/johnzon-websocket/pom.xml
+++ b/johnzon-websocket/pom.xml
@@ -21,7 +21,7 @@
<parent>
<artifactId>johnzon</artifactId>
<groupId>org.apache.johnzon</groupId>
- <version>1.2.1-SNAPSHOT</version>
+ <version>1.2.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/pom.xml b/pom.xml
index 2b3a152..6852af1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,14 +29,14 @@
<groupId>org.apache.johnzon</groupId>
<artifactId>johnzon</artifactId>
<packaging>pom</packaging>
- <version>1.2.1-SNAPSHOT</version>
+ <version>1.2.2-SNAPSHOT</version>
<name>Apache Johnzon</name>
<description>Apache Johnzon is an implementation of JSR-353 (JavaTM API for JSON Processing).</description>
<inceptionYear>2014</inceptionYear>
<url>http://johnzon.apache.org</url>
<properties>
- <geronimo-jsonp.version>1.2</geronimo-jsonp.version>
+ <geronimo-jsonp.version>1.3</geronimo-jsonp.version>
<geronimo-jsonb.version>1.2</geronimo-jsonb.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<johnzon.site.url>https://svn.apache.org/repos/asf/johnzon/site/publish/</johnzon.site.url>
diff --git a/src/site/markdown/download.md b/src/site/markdown/download.md
index 1ecb136..a206128 100644
--- a/src/site/markdown/download.md
+++ b/src/site/markdown/download.md
@@ -41,16 +41,16 @@
#### Binaries
The binary distribution contains all Johnzon modules.
-* [apache-johnzon-1.1.12-bin.zip](https://www.apache.org/dyn/closer.lua/johnzon/johnzon-1.1.12/apache-johnzon-1.1.12-bin.zip)
-* [apache-johnzon-1.1.12-bin.zip.sha1](https://www.apache.org/dist/johnzon/johnzon-1.1.12/apache-johnzon-1.1.12-bin.zip.sha1)
-* [apache-johnzon-1.1.12-bin.zip.asc](https://www.apache.org/dist/johnzon/johnzon-1.1.12/apache-johnzon-1.1.12-bin.zip.asc)
+* [apache-johnzon-1.2.0-bin.zip](https://www.apache.org/dyn/closer.lua/johnzon/johnzon-1.2.0/apache-johnzon-1.2.0-bin.zip)
+* [apache-johnzon-1.2.0-bin.zip.sha1](https://www.apache.org/dist/johnzon/johnzon-1.2.0/apache-johnzon-1.2.0-bin.zip.sha1)
+* [apache-johnzon-1.2.0-bin.zip.asc](https://www.apache.org/dist/johnzon/johnzon-1.2.0/apache-johnzon-1.2.0-bin.zip.asc)
#### Source
Should you want to build any of the above binaries, this source bundle is the right one and covers them all.
-* [johnzon-1.1.12-source-release.zip](https://www.apache.org/dyn/closer.lua/johnzon/johnzon-1.1.12/johnzon-1.1.12-source-release.zip)
-* [johnzon-1.1.12-source-release.zip.sha1](https://www.apache.org/dist/johnzon/johnzon-1.1.12/johnzon-1.1.12-source-release.zip.sha1)
-* [johnzon-1.1.12-source-release.zip.asc](https://www.apache.org/dist/johnzon/johnzon-1.1.12/johnzon-1.1.12-source-release.zip.asc)
+* [johnzon-1.2.0-source-release.zip](https://www.apache.org/dyn/closer.lua/johnzon/johnzon-1.2.0/johnzon-1.2.0-source-release.zip)
+* [johnzon-1.2.0-source-release.zip.sha1](https://www.apache.org/dist/johnzon/johnzon-1.2.0/johnzon-1.2.0-source-release.zip.sha1)
+* [johnzon-1.2.0-source-release.zip.asc](https://www.apache.org/dist/johnzon/johnzon-1.2.0/johnzon-1.2.0-source-release.zip.asc)
## Johnzon-1.0.x
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 @@
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 @@
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`: