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/02/19 08:43:08 UTC

[johnzon] branch master updated: JOHNZON-201 ensure we fail by default for @JsonbCreator if some arguments are missing

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 6c610cf  JOHNZON-201 ensure we fail by default for @JsonbCreator if some arguments are missing
6c610cf is described below

commit 6c610cf03164236c39214873001bc46f513c5eda
Author: Romain Manni-Bucau <rm...@gmail.com>
AuthorDate: Tue Feb 19 09:43:02 2019 +0100

    JOHNZON-201 ensure we fail by default for @JsonbCreator if some arguments are missing
---
 .../jaxrs/jsonb/jaxrs/JsonbJaxrsProvider.java      |  5 ++
 .../jsonb/DefaultPropertyVisibilityStrategy.java   | 90 ++++++++++++++++++++++
 .../org/apache/johnzon/jsonb/JohnzonBuilder.java   | 62 ++-------------
 .../org/apache/johnzon/jsonb/JsonbAccessMode.java  | 16 +++-
 .../johnzon/jsonb/DynamicBufferResizingTest.java   | 28 ++++++-
 5 files changed, 142 insertions(+), 59 deletions(-)

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 b59b697..9ef611c 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
@@ -133,6 +133,11 @@ public class JsonbJaxrsProvider<T> implements MessageBodyWriter<T>, MessageBodyR
         customized = true;
     }
 
+    public void setFailOnMissingCreatorValues(final boolean failOnMissingCreatorValues) {
+        config.setProperty("failOnMissingCreatorValues", failOnMissingCreatorValues);
+        customized = true;
+    }
+
     public void setInterfaceImplementationMapping(final Map<String, String> interfaceImplementationMapping) {
         final ClassLoader loader = Thread.currentThread().getContextClassLoader();
         final Function<String, Class<?>> load = name -> {
diff --git a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/DefaultPropertyVisibilityStrategy.java b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/DefaultPropertyVisibilityStrategy.java
new file mode 100644
index 0000000..482b957
--- /dev/null
+++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/DefaultPropertyVisibilityStrategy.java
@@ -0,0 +1,90 @@
+/*
+ * 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 java.util.Optional.ofNullable;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.json.bind.annotation.JsonbProperty;
+import javax.json.bind.annotation.JsonbVisibility;
+import javax.json.bind.config.PropertyVisibilityStrategy;
+
+public class DefaultPropertyVisibilityStrategy implements javax.json.bind.config.PropertyVisibilityStrategy {
+    private final ConcurrentMap<Class<?>, PropertyVisibilityStrategy> strategies = new ConcurrentHashMap<>();
+
+    @Override
+    public boolean isVisible(final Field field) {
+        if (field.getAnnotation(JsonbProperty.class) != null) {
+            return true;
+        }
+        final PropertyVisibilityStrategy strategy = strategies.computeIfAbsent(
+                field.getDeclaringClass(), this::visibilityStrategy);
+        return strategy == this ? Modifier.isPublic(field.getModifiers()) : strategy.isVisible(field);
+    }
+
+    @Override
+    public boolean isVisible(final Method method) {
+        final PropertyVisibilityStrategy strategy = strategies.computeIfAbsent(
+                method.getDeclaringClass(), this::visibilityStrategy);
+        return strategy == this ? Modifier.isPublic(method.getModifiers()) : strategy.isVisible(method);
+    }
+
+    private PropertyVisibilityStrategy visibilityStrategy(final Class<?> type) { // can be cached
+        JsonbVisibility visibility = type.getAnnotation(JsonbVisibility.class);
+        if (visibility != null) {
+            try {
+                return visibility.value().newInstance();
+            } catch (final InstantiationException | IllegalAccessException e) {
+                throw new IllegalArgumentException(e);
+            }
+        }
+        Package p = type.getPackage();
+        while (p != null) {
+            visibility = p.getAnnotation(JsonbVisibility.class);
+            if (visibility != null) {
+                try {
+                    return visibility.value().newInstance();
+                } catch (final InstantiationException | IllegalAccessException e) {
+                    throw new IllegalArgumentException(e);
+                }
+            }
+            final String name = p.getName();
+            final int end = name.lastIndexOf('.');
+            if (end < 0) {
+                break;
+            }
+            final String parentPack = name.substring(0, end);
+            p = Package.getPackage(parentPack);
+            if (p == null) {
+                try {
+                    p = ofNullable(type.getClassLoader()).orElseGet(ClassLoader::getSystemClassLoader)
+                                                         .loadClass(parentPack + ".package-info").getPackage();
+                } catch (final ClassNotFoundException e) {
+                    // no-op
+                }
+            }
+        }
+        return this;
+    }
+}
diff --git a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java
index e533923..a12608d 100644
--- a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java
+++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java
@@ -32,10 +32,7 @@ import static javax.json.bind.config.PropertyNamingStrategy.IDENTITY;
 import static javax.json.bind.config.PropertyOrderStrategy.LEXICOGRAPHICAL;
 
 import java.io.Closeable;
-import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.nio.charset.StandardCharsets;
@@ -66,8 +63,6 @@ import java.util.Objects;
 import java.util.Optional;
 import java.util.SimpleTimeZone;
 import java.util.TimeZone;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Supplier;
@@ -77,8 +72,6 @@ import javax.json.bind.Jsonb;
 import javax.json.bind.JsonbBuilder;
 import javax.json.bind.JsonbConfig;
 import javax.json.bind.adapter.JsonbAdapter;
-import javax.json.bind.annotation.JsonbProperty;
-import javax.json.bind.annotation.JsonbVisibility;
 import javax.json.bind.config.BinaryDataStrategy;
 import javax.json.bind.config.PropertyNamingStrategy;
 import javax.json.bind.config.PropertyVisibilityStrategy;
@@ -153,53 +146,7 @@ public class JohnzonBuilder implements JsonbBuilder {
         final PropertyNamingStrategy propertyNamingStrategy = new PropertyNamingStrategyFactory(namingStrategyValue.orElse(IDENTITY)).create();
         final String orderValue = config.getProperty(JsonbConfig.PROPERTY_ORDER_STRATEGY).map(String::valueOf).orElse(LEXICOGRAPHICAL);
         final PropertyVisibilityStrategy visibilityStrategy = config.getProperty(JsonbConfig.PROPERTY_VISIBILITY_STRATEGY)
-                .map(PropertyVisibilityStrategy.class::cast).orElse(new PropertyVisibilityStrategy() {
-                    private final ConcurrentMap<Class<?>, PropertyVisibilityStrategy> strategies = new ConcurrentHashMap<>();
-
-                    @Override
-                    public boolean isVisible(final Field field) {
-                        if (field.getAnnotation(JsonbProperty.class) != null) {
-                            return true;
-                        }
-                        final PropertyVisibilityStrategy strategy = strategies.computeIfAbsent(field.getDeclaringClass(), this::visibilityStrategy);
-                        return strategy == this ? Modifier.isPublic(field.getModifiers()) : strategy.isVisible(field);
-                    }
-
-                    @Override
-                    public boolean isVisible(final Method method) {
-                        final PropertyVisibilityStrategy strategy = strategies.computeIfAbsent(method.getDeclaringClass(), this::visibilityStrategy);
-                        return strategy == this ? Modifier.isPublic(method.getModifiers()) : strategy.isVisible(method);
-                    }
-
-                    private PropertyVisibilityStrategy visibilityStrategy(final Class<?> type) { // can be cached
-                        JsonbVisibility visibility = type.getAnnotation(JsonbVisibility.class);
-                        if (visibility != null) {
-                            try {
-                                return visibility.value().newInstance();
-                            } catch (final InstantiationException | IllegalAccessException e) {
-                                throw new IllegalArgumentException(e);
-                            }
-                        }
-                        Package p = type.getPackage();
-                        while (p != null) {
-                            visibility = p.getAnnotation(JsonbVisibility.class);
-                            if (visibility != null) {
-                                try {
-                                    return visibility.value().newInstance();
-                                } catch (final InstantiationException | IllegalAccessException e) {
-                                    throw new IllegalArgumentException(e);
-                                }
-                            }
-                            final String name = p.getName();
-                            final int end = name.lastIndexOf('.');
-                            if (end < 0) {
-                                break;
-                            }
-                            p = Package.getPackage(name.substring(0, end));
-                        }
-                        return this;
-                    }
-                });
+                .map(PropertyVisibilityStrategy.class::cast).orElse(new DefaultPropertyVisibilityStrategy());
 
         config.getProperty("johnzon.attributeOrder").ifPresent(comp -> builder.setAttributeOrder(Comparator.class.cast(comp)));
         config.getProperty("johnzon.enforceQuoteString")
@@ -250,7 +197,10 @@ public class JohnzonBuilder implements JsonbBuilder {
                         factory, parserFactoryProvider,
                         config.getProperty("johnzon.accessModeDelegate")
                                 .map(this::toAccessMode)
-                                .orElseGet(() -> new FieldAndMethodAccessMode(true, true, false))));
+                                .orElseGet(() -> new FieldAndMethodAccessMode(true, true, false)),
+                        config.getProperty("johnzon.failOnMissingCreatorValues")
+                              .map(it -> String.class.isInstance(it) ? Boolean.parseBoolean(it.toString()) : Boolean.class.cast(it))
+                              .orElse(true) /*spec 1.0 requirement*/));
         builder.setAccessMode(accessMode);
 
         // user adapters
@@ -425,7 +375,7 @@ public class JohnzonBuilder implements JsonbBuilder {
 
     private Object getBeanManager() {
         if (beanManager == null) {
-            try { // don't trigger CDI is not there
+            try { // don't trigger CDI if not there
                 final Class<?> cdi = tccl().loadClass("javax.enterprise.inject.spi.CDI");
                 final Object cdiInstance = cdi.getMethod("current").invoke(null);
                 beanManager = cdi.getMethod("getBeanManager").invoke(cdiInstance);
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 4526c72..e5718fb 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
@@ -83,6 +83,7 @@ import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.OptionalDouble;
 import java.util.OptionalInt;
@@ -91,6 +92,7 @@ import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Supplier;
 import java.util.stream.Stream;
@@ -121,11 +123,13 @@ public class JsonbAccessMode implements AccessMode, Closeable {
             throw new UnsupportedOperationException();
         }
     };
+    private boolean failOnMissingCreatorValues;
 
     public JsonbAccessMode(final PropertyNamingStrategy propertyNamingStrategy, final String orderValue,
                            final PropertyVisibilityStrategy visibilityStrategy, final boolean caseSensitive,
                            final Map<AdapterKey, Adapter<?, ?>> defaultConverters, final JohnzonAdapterFactory factory,
-                           final Supplier<JsonParserFactory> parserFactory, final AccessMode delegate) {
+                           final Supplier<JsonParserFactory> parserFactory, final AccessMode delegate,
+                           final boolean failOnMissingCreatorValues) {
         this.naming = propertyNamingStrategy;
         this.order = orderValue;
         this.visibility = visibilityStrategy;
@@ -134,6 +138,7 @@ public class JsonbAccessMode implements AccessMode, Closeable {
         this.defaultConverters = defaultConverters;
         this.factory = factory;
         this.parserFactory = parserFactory;
+        this.failOnMissingCreatorValues = failOnMissingCreatorValues;
     }
 
     @Override
@@ -165,6 +170,13 @@ public class JsonbAccessMode implements AccessMode, Closeable {
         }
         final Constructor<?> finalConstructor = constructor;
         final Method finalFactory = factory;
+        final Consumer<Object[]> factoryValidator = failOnMissingCreatorValues ?
+                args -> {
+                    if (args == null || Stream.of(args).anyMatch(Objects::isNull)) {
+                        throw new JsonbException("Missing @JsonbCreator argument");
+                    }
+                } :
+                args -> {};
         final Type[] types;
         final String[] params;
         final Adapter<?, ?>[] converters;
@@ -225,6 +237,7 @@ public class JsonbAccessMode implements AccessMode, Closeable {
                         new Factory() {
                             @Override
                             public Object create(final Object[] params) {
+                                factoryValidator.accept(params);
                                 try {
                                     return finalConstructor.newInstance(params);
                                 } catch (final InstantiationException | IllegalAccessException e) {
@@ -262,6 +275,7 @@ public class JsonbAccessMode implements AccessMode, Closeable {
                         new Factory() {
                             @Override
                             public Object create(final Object[] params) {
+                                factoryValidator.accept(params);
                                 try {
                                     final Object invoke = finalFactory.invoke(null, params);
                                     if (!clazz.isInstance(invoke)) {
diff --git a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/DynamicBufferResizingTest.java b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/DynamicBufferResizingTest.java
index 6345a9b..6e4d378 100644
--- a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/DynamicBufferResizingTest.java
+++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/DynamicBufferResizingTest.java
@@ -19,10 +19,12 @@
 package org.apache.johnzon.jsonb;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 
 import javax.json.bind.Jsonb;
 import javax.json.bind.JsonbBuilder;
 import javax.json.bind.JsonbConfig;
+import javax.json.bind.JsonbException;
 import javax.json.bind.annotation.JsonbCreator;
 import javax.json.bind.annotation.JsonbProperty;
 import javax.json.bind.annotation.JsonbPropertyOrder;
@@ -32,14 +34,14 @@ import org.junit.Test;
 
 public class DynamicBufferResizingTest {
     @Test
-    public void main() {
+    public void main() throws Exception {
         final JsonbConfig config = new JsonbConfig()
                 .withFormatting(Boolean.TRUE)
                 .withBinaryDataStrategy(BinaryDataStrategy.BASE_64);
         Jsonb jsonb = JsonbBuilder.create(config);
 
         final Request request = new Request("Screenshot.png", "image/png", new byte[558140]);
-        String json = jsonb.toJson(request);
+        final String json = jsonb.toJson(request);
 
         // the first call works
         for (int i = 0; i < 10; i++) { // originally the second call was failling
@@ -48,6 +50,28 @@ public class DynamicBufferResizingTest {
             assertEquals("image/png", fromJson.mimeType);
             assertEquals(558140, fromJson.body.length);
         }
+
+        jsonb.close();
+    }
+
+    @Test(expected = JsonbException.class)
+    public void failOnMissingProp() throws Exception {
+        try (final Jsonb jsonb = JsonbBuilder.create()) {
+            jsonb.fromJson(jsonb.toJson(new Request("Screenshot.png", null, null)), Request.class);
+        }
+    }
+
+    @Test
+    public void dontFailOnMissingPropsIfConfiguredAsSuch() throws Exception {
+        try (final Jsonb jsonb = JsonbBuilder.create(new JsonbConfig().setProperty("johnzon.failOnMissingCreatorValues", false))) {
+            final Request request = new Request("Screenshot.png", null, null);
+            final String json = jsonb.toJson(request);
+
+            final Request fromJson = jsonb.fromJson(json, Request.class);
+            assertEquals("Screenshot.png", fromJson.name);
+            assertNull(fromJson.mimeType);
+            assertNull(fromJson.body);
+        }
     }
 
     @JsonbPropertyOrder(value = {"name", "mimeType"})