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 aaa2af9  [JOHNZON-337] avoid jsonb deserializers to loop
aaa2af9 is described below

commit aaa2af985b51517f6d636d3a28b176f3109c5958
Author: Romain Manni-Bucau <rmannibu...@gmail.com>
AuthorDate: Fri Mar 5 14:41:14 2021 +0100

    [JOHNZON-337] avoid jsonb deserializers to loop
---
 .../apache/johnzon/RecursivePolymorphismTest.java  | 131 +++++++++++++++++++++
 .../org/apache/johnzon/mapper/MappingParser.java   |   6 +-
 .../apache/johnzon/mapper/MappingParserImpl.java   |  66 +++++++----
 3 files changed, 182 insertions(+), 21 deletions(-)

diff --git 
a/johnzon-jsonb/src/test/java/org/apache/johnzon/RecursivePolymorphismTest.java 
b/johnzon-jsonb/src/test/java/org/apache/johnzon/RecursivePolymorphismTest.java
new file mode 100644
index 0000000..75996ca
--- /dev/null
+++ 
b/johnzon-jsonb/src/test/java/org/apache/johnzon/RecursivePolymorphismTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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;
+
+import org.junit.Test;
+
+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.bind.serializer.JsonbSerializer;
+import javax.json.bind.serializer.SerializationContext;
+import javax.json.stream.JsonGenerator;
+import javax.json.stream.JsonParser;
+import java.lang.reflect.Type;
+
+import static javax.json.stream.JsonParser.Event.KEY_NAME;
+import static javax.json.stream.JsonParser.Event.START_OBJECT;
+import static javax.json.stream.JsonParser.Event.VALUE_NUMBER;
+import static org.junit.Assert.assertEquals;
+
+public class RecursivePolymorphismTest {
+    @Test
+    public void read() throws Exception {
+        try (final Jsonb jsonb = JsonbBuilder.create(new JsonbConfig()
+                .withDeserializers(new PolyDeserializer()))) {
+            final Parent parent = 
jsonb.fromJson("{\"type\":1,\"name\":\"first\",\"uno\":true,\"duo\":true}", 
Parent.class);
+            assertEquals("Child1{name='first', uno=true}", parent.toString());
+        }
+    }
+
+    @Test
+    public void write() throws Exception {
+        final Child1 child1 = new Child1();
+        child1.name = "first";
+        child1.uno = true;
+        try (final Jsonb jsonb = JsonbBuilder.create(new JsonbConfig()
+                .withSerializers(new PolySerializer()))) {
+            final String json = jsonb.toJson(child1);
+            assertEquals("{\"type\":1,\"name\":\"first\",\"uno\":true}", json);
+        }
+    }
+
+    public static class Parent {
+        public String name;
+
+        @Override
+        public String toString() {
+            return "Parent{name='" + name + "'}";
+        }
+    }
+
+    public static class Child1 extends Parent {
+        public boolean uno;
+
+        @Override
+        public String toString() {
+            return "Child1{name='" + name + "', uno=" + uno + '}';
+        }
+    }
+
+    public static class Child2 extends Parent {
+        public boolean duo;
+
+        @Override
+        public String toString() {
+            return "Child2{name='" + name + "', duo=" + duo + '}';
+        }
+    }
+
+    public static class PolySerializer implements JsonbSerializer<Parent> {
+        @Override
+        public void serialize(final Parent obj, final JsonGenerator generator, 
final SerializationContext ctx) {
+            generator.writeStartObject();
+            generator.write("type", Child1.class.isInstance(obj) ? 1 : 2);
+            ctx.serialize(obj, generator);
+            generator.writeEnd();
+        }
+    }
+
+    public static class PolyDeserializer implements JsonbDeserializer<Parent> {
+        @Override
+        public Parent deserialize(final JsonParser parser,
+                                  final DeserializationContext ctx,
+                                  final Type rtType) {
+            moveToType(parser);
+            final int type = parser.getInt();
+            switch (type) {
+                case 1:
+                    return ctx.deserialize(Child1.class, parser);
+                case 2:
+                    return ctx.deserialize(Child2.class, parser);
+                default:
+                    throw new IllegalArgumentException(String.valueOf(type));
+            }
+        }
+
+        private void moveToType(final JsonParser parser) {
+            ensureNext(parser, START_OBJECT);
+            ensureNext(parser, KEY_NAME);
+            if (!"type".equals(parser.getString())) {
+                throw new IllegalArgumentException("Expected 'type' but got '" 
+ parser.getString() + "'");
+            }
+            ensureNext(parser, VALUE_NUMBER);
+        }
+
+        private void ensureNext(final JsonParser parser, final 
JsonParser.Event expected) {
+            final JsonParser.Event next = parser.next();
+            if (expected != next) {
+                throw new IllegalArgumentException(next + " != " + expected);
+            }
+        }
+    }
+}
diff --git 
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParser.java 
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParser.java
index c129264..7a265a6 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParser.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParser.java
@@ -20,14 +20,18 @@ package org.apache.johnzon.mapper;
 
 import javax.json.JsonValue;
 import java.lang.reflect.Type;
+import java.util.Collection;
 
 /**
  * Handles reading Json for Objects.
- *
  */
 public interface MappingParser {
 
     <T> T readObject(Type targetType);
 
     <T> T readObject(JsonValue jsonValue, Type targetType);
+
+    default Collection<Class<?>> getSkippedConverters() {
+        return null;
+    }
 }
diff --git 
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java 
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
index 71e0848..80ca4ad 100644
--- 
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
+++ 
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
@@ -136,18 +136,22 @@ public class MappingParserImpl implements MappingParser {
     }
 
     @Override
-    public <T> T readObject(JsonValue jsonValue, Type targetType) {
-        return readObject(jsonValue, targetType, targetType instanceof Class 
|| targetType instanceof ParameterizedType);
+    public <T> T readObject(final JsonValue jsonValue, final Type targetType) {
+        return readObject(jsonValue, targetType, targetType instanceof Class 
|| targetType instanceof ParameterizedType, null);
     }
 
-    private <T> T readObject(JsonValue jsonValue, Type targetType, boolean 
applyObjectConverter) {
+    public <T> T readObject(final JsonValue jsonValue, final Type targetType, 
final boolean applyObjectConverter,
+                            final Collection<Class<?>> skippedConverters) {
         final JsonValue.ValueType valueType = jsonValue != null ? 
jsonValue.getValueType() : null;
 
         if (JsonStructure.class == targetType || JsonObject.class == 
targetType || JsonValue.class == targetType) {
             return (T) jsonValue;
         }
         if (JsonObject.class.isInstance(jsonValue)) {
-            return (T) buildObject(targetType, 
JsonObject.class.cast(jsonValue), applyObjectConverter, isDeduplicateObjects ? 
new JsonPointerTracker(null, "/") : null);
+            return (T) buildObject(
+                    targetType, JsonObject.class.cast(jsonValue), 
applyObjectConverter,
+                    isDeduplicateObjects ? new JsonPointerTracker(null, "/") : 
null,
+                    skippedConverters);
         }
         if (JsonString.class.isInstance(jsonValue)) {
             if ((targetType == String.class || targetType == Object.class)) {
@@ -221,7 +225,7 @@ public class MappingParserImpl implements MappingParser {
                             isDeduplicateObjects ? new 
JsonPointerTracker(null, "/") : null, Object.class);
                 }
                 if (Collection.class.isAssignableFrom(asClass)) {
-                    return readObject(jsonValue, new 
JohnzonParameterizedType(asClass, Object.class), applyObjectConverter);
+                    return readObject(jsonValue, new 
JohnzonParameterizedType(asClass, Object.class), applyObjectConverter, 
skippedConverters);
                 }
             }
             if (ParameterizedType.class.isInstance(targetType)) {
@@ -256,7 +260,7 @@ public class MappingParserImpl implements MappingParser {
 
 
     private Object buildObject(final Type inType, final JsonObject object, 
final boolean applyObjectConverter,
-                               final JsonPointerTracker jsonPointer) {
+                               final JsonPointerTracker jsonPointer, final 
Collection<Class<?>> skippedConverters) {
         Type type = inType;
         if (inType == Object.class) {
             type = new JohnzonParameterizedType(Map.class, String.class, 
Object.class);
@@ -268,9 +272,16 @@ public class MappingParserImpl implements MappingParser {
                 throw new MapperException("ObjectConverters are only supported 
for Classes not Types");
             }
 
-            ObjectConverter.Reader objectConverter = 
config.findObjectConverterReader((Class) type);
-            if (objectConverter != null) {
-                return objectConverter.fromJson(object, type, new 
SuppressConversionMappingParser(this, object));
+            final Class clazz = (Class) type;
+            if (skippedConverters == null || 
!skippedConverters.contains(clazz)) {
+                ObjectConverter.Reader objectConverter = 
config.findObjectConverterReader(clazz);
+                if (objectConverter != null) {
+                    final Collection<Class<?>> skipped = skippedConverters == 
null ? new ArrayList<>() : skippedConverters;
+                    skipped.add(clazz);
+                    return objectConverter.fromJson(
+                            object, type,
+                            new SuppressConversionMappingParser(this, object, 
skipped));
+                }
             }
         }
 
@@ -280,7 +291,7 @@ public class MappingParserImpl implements MappingParser {
                 final String discriminator = 
object.getString(config.getDiscriminator());
                 final Class<?> nestedType = 
config.getTypeLoader().apply(discriminator);
                 if (nestedType != null && nestedType != inType) {
-                    return buildObject(nestedType, object, 
applyObjectConverter, jsonPointer);
+                    return buildObject(nestedType, object, 
applyObjectConverter, jsonPointer, skippedConverters);
                 }
             }
         }
@@ -346,8 +357,12 @@ public class MappingParserImpl implements MappingParser {
             throw new MapperException("Can't map " + type);
         }
 
-        if (applyObjectConverter && classMapping.reader != null) {
-            return classMapping.reader.fromJson(object, type, new 
SuppressConversionMappingParser(this, object));
+        if (applyObjectConverter && classMapping.reader != null && 
(skippedConverters == null || !skippedConverters.contains(type))) {
+            final Collection<Class<?>> skipped = skippedConverters == null ? 
new ArrayList<>() : skippedConverters;
+            if (Class.class.isInstance(type)) { // more than likely, drop this 
check?
+                skipped.add(Class.class.cast(type));
+            }
+            return classMapping.reader.fromJson(object, type, new 
SuppressConversionMappingParser(this, object, skipped));
         }
         /* doesn't work yet
         if (classMapping.adapter != null) {
@@ -502,7 +517,7 @@ public class MappingParserImpl implements MappingParser {
             final Object param;
             try {
                 Type to = key.getTo();
-                param = buildObject(to, JsonObject.class.cast(jsonValue), to 
instanceof Class, jsonPointer);
+                param = buildObject(to, JsonObject.class.cast(jsonValue), to 
instanceof Class, jsonPointer, getSkippedConverters());
             } catch (final Exception e) {
                 throw new MapperException(e);
             }
@@ -648,7 +663,7 @@ public class MappingParserImpl implements MappingParser {
                     baseInstance != null ? baseInstance.getClass() : (
                             typedAdapter ? 
TypeAwareAdapter.class.cast(itemConverter).getTo() : type),
                     JsonObject.class.cast(jsonValue), type instanceof Class,
-                    jsonPointer);
+                    jsonPointer, getSkippedConverters());
             return typedAdapter ? itemConverter.to(object) : object;
         } else if (JsonArray.class.isInstance(jsonValue)) {
             if (JsonArray.class == type || JsonStructure.class == type || 
JsonValue.class == type) {
@@ -1151,24 +1166,35 @@ public class MappingParserImpl implements MappingParser 
{
     private static class SuppressConversionMappingParser implements 
MappingParser {
         private final MappingParserImpl delegate;
         private final JsonObject suppressConversionFor;
+        private final Collection<Class<?>> skippedConverters;
 
-        public SuppressConversionMappingParser(MappingParserImpl delegate, 
JsonObject suppressConversionFor) {
+        public SuppressConversionMappingParser(final MappingParserImpl 
delegate, final JsonObject suppressConversionFor,
+                                               final Collection<Class<?>> 
skippedConverters) {
             this.delegate = delegate;
             this.suppressConversionFor = suppressConversionFor;
+            this.skippedConverters = skippedConverters;
+        }
+
+        @Override
+        public Collection<Class<?>> getSkippedConverters() {
+            return skippedConverters;
         }
 
         @Override
-        public <T> T readObject(Type targetType) {
+        public <T> T readObject(final Type targetType) {
             return delegate.readObject(targetType);
         }
 
         @Override
-        public <T> T readObject(JsonValue jsonValue, Type targetType) {
+        public <T> T readObject(final JsonValue jsonValue, final Type 
targetType) {
+            final Collection<Class<?>> skippedConverters = 
getSkippedConverters();
             if (suppressConversionFor == jsonValue) {
-                return delegate.readObject(jsonValue, targetType, false);
+                return delegate.readObject(jsonValue, targetType, false, 
skippedConverters);
             }
-            return delegate.readObject(jsonValue, targetType);
+            final boolean useConverters = (Class.class.isInstance(targetType) 
&&
+                    (skippedConverters == null || 
skippedConverters.stream().noneMatch(it -> 
it.isAssignableFrom(Class.class.cast(targetType))))) ||
+                    ParameterizedType.class.isInstance(targetType);
+            return delegate.readObject(jsonValue, targetType, useConverters, 
skippedConverters);
         }
     }
-
 }

Reply via email to