Repository: johnzon
Updated Branches:
  refs/heads/master f9a916200 -> 5f5b40906


JOHNZON-170 adding polymorphic extension


Project: http://git-wip-us.apache.org/repos/asf/johnzon/repo
Commit: http://git-wip-us.apache.org/repos/asf/johnzon/commit/5f5b4090
Tree: http://git-wip-us.apache.org/repos/asf/johnzon/tree/5f5b4090
Diff: http://git-wip-us.apache.org/repos/asf/johnzon/diff/5f5b4090

Branch: refs/heads/master
Commit: 5f5b40906d97d67d9ee7d9acf970fb5d6a933758
Parents: f9a9162
Author: Romain Manni-Bucau <[email protected]>
Authored: Mon Apr 23 20:02:46 2018 +0200
Committer: Romain Manni-Bucau <[email protected]>
Committed: Mon Apr 23 20:02:46 2018 +0200

----------------------------------------------------------------------
 johnzon-json-extras/pom.xml                     |  51 +++++
 .../jsonb/extras/polymorphism/Polymorphic.java  | 206 +++++++++++++++++++
 .../extras/polymorphism/PolymorphicTest.java    | 111 ++++++++++
 johnzon-jsonb/pom.xml                           |   1 -
 .../JohnzonDeserializationContext.java          |   2 +-
 pom.xml                                         |   1 +
 src/site/markdown/index.md                      |  48 +++++
 7 files changed, 418 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/johnzon/blob/5f5b4090/johnzon-json-extras/pom.xml
----------------------------------------------------------------------
diff --git a/johnzon-json-extras/pom.xml b/johnzon-json-extras/pom.xml
new file mode 100644
index 0000000..c0a4a94
--- /dev/null
+++ b/johnzon-json-extras/pom.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; xsi:schemaLocation=" 
http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+  <parent>
+    <artifactId>johnzon</artifactId>
+    <groupId>org.apache.johnzon</groupId>
+    <version>1.1.8-SNAPSHOT</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+
+  <artifactId>johnzon-jsonb-extras</artifactId>
+  <name>Johnzon :: JSON-B Extensions</name>
+  <packaging>bundle</packaging>
+
+  <properties>
+    
<staging.directory>${project.parent.reporting.outputDirectory}</staging.directory>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.geronimo.specs</groupId>
+      <artifactId>geronimo-jsonb_1.0_spec</artifactId>
+      <version>1.0</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.johnzon</groupId>
+      <artifactId>johnzon-jsonb</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>

http://git-wip-us.apache.org/repos/asf/johnzon/blob/5f5b4090/johnzon-json-extras/src/main/java/org/apache/johnzon/jsonb/extras/polymorphism/Polymorphic.java
----------------------------------------------------------------------
diff --git 
a/johnzon-json-extras/src/main/java/org/apache/johnzon/jsonb/extras/polymorphism/Polymorphic.java
 
b/johnzon-json-extras/src/main/java/org/apache/johnzon/jsonb/extras/polymorphism/Polymorphic.java
new file mode 100644
index 0000000..6affc96
--- /dev/null
+++ 
b/johnzon-json-extras/src/main/java/org/apache/johnzon/jsonb/extras/polymorphism/Polymorphic.java
@@ -0,0 +1,206 @@
+/*
+ * 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.extras.polymorphism;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static java.util.Objects.requireNonNull;
+import static java.util.function.Function.identity;
+import static java.util.stream.Collectors.toMap;
+
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.stream.Stream;
+
+import javax.json.bind.annotation.JsonbProperty;
+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;
+
+public final class Polymorphic {
+    private Polymorphic() {
+        // no-op
+    }
+
+    private static String getId(final Class<?> type) {
+        final JsonId mapping = type.getAnnotation(JsonId.class);
+        if (mapping == null) {
+            throw new IllegalArgumentException("No @Id on " + type);
+        }
+        final String id = mapping.value();
+        return id.isEmpty() ? type.getSimpleName() : id;
+    }
+
+    public static class Serializer<T> implements JsonbSerializer<T> {
+        private transient volatile ConcurrentMap<Class<?>, String> idMapping = 
new ConcurrentHashMap<>();
+
+        @Override
+        public void serialize(final T obj, final JsonGenerator generator, 
final SerializationContext ctx) {
+            ensureInit();
+            ctx.serialize(new Wrapper<>(getOrLoadId(obj), obj), generator);
+        }
+
+        private String getOrLoadId(final T obj) {
+            final Class<?> type = obj.getClass();
+            String id = idMapping.get(type);
+            if (id == null) {
+                id = getId(type);
+                idMapping.putIfAbsent(type, id);
+            }
+            return id;
+        }
+
+        private void ensureInit() {
+            if (idMapping == null) {
+                synchronized (this) {
+                    if (idMapping == null) {
+                        idMapping = new ConcurrentHashMap<>();
+                    }
+                }
+            }
+        }
+    }
+
+    public static class DeSerializer<T> implements JsonbDeserializer<T> {
+        private transient volatile ConcurrentMap<String, Type> classMapping = 
new ConcurrentHashMap<>();
+
+        @Override
+        public T deserialize(final JsonParser parser, final 
DeserializationContext ctx, final Type rtType) {
+            ensureInit();
+            if (classMapping == null || classMapping.isEmpty()) {
+                synchronized (this) {
+                    if (classMapping == null || classMapping.isEmpty()) {
+                        loadMapping(rtType);
+                    }
+                }
+            }
+            if (!parser.hasNext()) {
+                return null;
+            }
+            eatStartObject(parser);
+            eatTypeKey(parser);
+            final String typeId = getTypeValue(parser);
+            eatValueStart(parser);
+            final Type type = requireNonNull(classMapping.get(typeId), "No 
mapping for " + typeId);
+            parser.next();
+            return (T) ctx.deserialize(type, parser);
+        }
+
+        private void loadMapping(final Type rtType) {
+            final Class<?> from;
+            if (ParameterizedType.class.isInstance(rtType)) {
+                final Type rawType = 
ParameterizedType.class.cast(rtType).getRawType();
+                if (!Class.class.isInstance(rawType)) {
+                    throw new IllegalStateException("Unsupported type: " + 
rawType);
+                }
+                from = Class.class.cast(rawType);
+            } else if (Class.class.isInstance(rtType)) {
+                from = Class.class.cast(rtType);
+            } else {
+                throw new IllegalStateException("Unsupported type: " + rtType);
+            }
+
+            final JsonChildren classes = 
from.getAnnotation(JsonChildren.class);
+            if (classes == null) {
+                throw new IllegalArgumentException("No @Classes on " + from);
+            }
+
+            classMapping.putAll(Stream.of(classes.value())
+                    .collect(toMap(Polymorphic::getId, identity())));
+        }
+
+        private void eatStartObject(final JsonParser parser) {
+            if (parser.next() != JsonParser.Event.START_OBJECT) {
+                throw new IllegalArgumentException("Invalid JSON, expected 
START_OBJECT");
+            }
+        }
+
+        private void eatTypeKey(final JsonParser parser) {
+            if (!parser.hasNext() || parser.next() != 
JsonParser.Event.KEY_NAME) {
+                throw new IllegalArgumentException("Invalid JSON, expected 
KEY_NAME");
+            }
+            if (!"_type".equals(parser.getString())) {
+                throw new IllegalArgumentException("Expected key _type");
+            }
+        }
+
+        private void eatValueStart(final JsonParser parser) {
+            if (!parser.hasNext() || parser.next() != 
JsonParser.Event.KEY_NAME) {
+                throw new IllegalArgumentException("Invalid JSON, expected 
KEY_NAME");
+            }
+            if (!parser.hasNext() || !"_value".equals(parser.getString())) {
+                throw new IllegalArgumentException("Expected key _value");
+            }
+        }
+
+        private String getTypeValue(final JsonParser parser) {
+            final JsonParser.Event next = parser.next();
+            if (!parser.hasNext() || next != JsonParser.Event.VALUE_STRING) {
+                throw new IllegalArgumentException("Unexpected event " + next);
+            }
+            return parser.getString();
+        }
+
+        private void ensureInit() {
+            if (classMapping == null) {
+                synchronized (this) {
+                    if (classMapping == null) {
+                        classMapping = new ConcurrentHashMap<>();
+                    }
+                }
+            }
+        }
+    }
+
+    public static class Wrapper<T> {
+        @JsonbProperty("_type")
+        public String id;
+
+        @JsonbProperty("_value")
+        public T value;
+
+        private Wrapper(final String id, final T obj) {
+            this.id = id;
+            this.value = obj;
+        }
+    }
+
+    @Inherited
+    @Retention(RUNTIME)
+    public @interface JsonChildren {
+        /**
+         * @return the list of leaf classes which can be instantiated by the 
children.
+         */
+        Class<?>[] value();
+    }
+
+    @Target(TYPE)
+    @Retention(RUNTIME)
+    public @interface JsonId {
+        String value() default "";
+    }
+}

http://git-wip-us.apache.org/repos/asf/johnzon/blob/5f5b4090/johnzon-json-extras/src/test/java/org/apache/johnzon/jsonb/extras/polymorphism/PolymorphicTest.java
----------------------------------------------------------------------
diff --git 
a/johnzon-json-extras/src/test/java/org/apache/johnzon/jsonb/extras/polymorphism/PolymorphicTest.java
 
b/johnzon-json-extras/src/test/java/org/apache/johnzon/jsonb/extras/polymorphism/PolymorphicTest.java
new file mode 100644
index 0000000..1b34443
--- /dev/null
+++ 
b/johnzon-json-extras/src/test/java/org/apache/johnzon/jsonb/extras/polymorphism/PolymorphicTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.extras.polymorphism;
+
+import static java.util.Arrays.asList;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
+import java.util.List;
+
+import javax.json.bind.Jsonb;
+import javax.json.bind.JsonbBuilder;
+import javax.json.bind.JsonbConfig;
+import javax.json.bind.annotation.JsonbTypeDeserializer;
+import javax.json.bind.annotation.JsonbTypeSerializer;
+import javax.json.bind.config.PropertyOrderStrategy;
+
+import org.junit.Test;
+
+public class PolymorphicTest {
+    private static final String JSON = 
"{\"root\":{\"_type\":\"first\",\"_value\":{\"name\":\"simple\",\"type\":\"c1\"}},"
 +
+            
"\"roots\":[{\"_type\":\"first\",\"_value\":{\"name\":\"c-simple\",\"type\":\"c1\"}},"
 +
+            
"{\"_type\":\"second\",\"_value\":{\"name\":\"c-other\",\"type\":2}}]}";
+
+    @Test
+    public void serialize() throws Exception {
+        final Child1 mainRoot = new Child1();
+        mainRoot.type = "c1";
+        mainRoot.name = "simple";
+        final Child1 roots1 = new Child1();
+        roots1.type = "c1";
+        roots1.name = "c-simple";
+        final Child2 roots2 = new Child2();
+        roots2.type = 2;
+        roots2.name = "c-other";
+        final Wrapper wrapper = new Wrapper();
+        wrapper.root = mainRoot;
+        wrapper.roots = asList(roots1, roots2);
+
+        try (final Jsonb jsonb = JsonbBuilder.create(new 
JsonbConfig().withPropertyOrderStrategy(PropertyOrderStrategy.LEXICOGRAPHICAL)))
 {
+            final String json = jsonb.toJson(wrapper);
+            assertEquals(JSON, json);
+        }
+    }
+
+    @Test
+    public void deserialize() throws Exception {
+        try (final Jsonb jsonb = JsonbBuilder.create(new 
JsonbConfig().withPropertyOrderStrategy(PropertyOrderStrategy.LEXICOGRAPHICAL)))
 {
+            final Wrapper wrapper = jsonb.fromJson(JSON, Wrapper.class);
+            assertNotNull(wrapper.root);
+            assertThat(wrapper.root, instanceOf(Child1.class));
+            assertEquals("simple", wrapper.root.name);
+            assertEquals("c1", Child1.class.cast(wrapper.root).type);
+
+            assertNotNull(wrapper.roots);
+            assertEquals(2, wrapper.roots.size());
+            assertThat(wrapper.roots.get(0), instanceOf(Child1.class));
+            assertThat(wrapper.roots.get(1), instanceOf(Child2.class));
+            assertEquals("c-simple", wrapper.roots.get(0).name);
+            assertEquals("c1", Child1.class.cast(wrapper.roots.get(0)).type);
+            assertEquals("c-other", wrapper.roots.get(1).name);
+            assertEquals(2, Child2.class.cast(wrapper.roots.get(1)).type);
+        }
+    }
+
+    @Polymorphic.JsonChildren({
+            Child1.class,
+            Child2.class
+    })
+    public static abstract class Root {
+        public String name;
+    }
+
+    @Polymorphic.JsonId("first")
+    public static class Child1 extends Root {
+        public String type;
+    }
+
+    @Polymorphic.JsonId("second")
+    public static class Child2 extends Root {
+        public int type;
+    }
+
+    public static class Wrapper {
+        @JsonbTypeSerializer(Polymorphic.Serializer.class)
+        @JsonbTypeDeserializer(Polymorphic.DeSerializer.class)
+        public Root root;
+
+        @JsonbTypeSerializer(Polymorphic.Serializer.class)
+        @JsonbTypeDeserializer(Polymorphic.DeSerializer.class)
+        public List<Root> roots;
+    }
+}

http://git-wip-us.apache.org/repos/asf/johnzon/blob/5f5b4090/johnzon-jsonb/pom.xml
----------------------------------------------------------------------
diff --git a/johnzon-jsonb/pom.xml b/johnzon-jsonb/pom.xml
index 87b88be..5bf0a65 100644
--- a/johnzon-jsonb/pom.xml
+++ b/johnzon-jsonb/pom.xml
@@ -30,7 +30,6 @@
   <packaging>bundle</packaging>
 
   <properties>
-    <java-compile.version>1.8</java-compile.version>
     
<staging.directory>${project.parent.reporting.outputDirectory}</staging.directory>
   </properties>
 

http://git-wip-us.apache.org/repos/asf/johnzon/blob/5f5b4090/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/serializer/JohnzonDeserializationContext.java
----------------------------------------------------------------------
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 929589c..004b440 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
@@ -45,6 +45,6 @@ public class JohnzonDeserializationContext implements 
DeserializationContext {
     }
 
     private JsonValue read(final JsonParser parser) { // TODO: use jsonp 1.1 
and not johnzon internals
-        return new JsonReaderImpl(parser).readValue();
+        return new JsonReaderImpl(parser, true).readValue();
     }
 }

http://git-wip-us.apache.org/repos/asf/johnzon/blob/5f5b4090/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index f39d348..6f31957 100644
--- a/pom.xml
+++ b/pom.xml
@@ -63,6 +63,7 @@
     <module>johnzon-maven-plugin</module>
     <module>johnzon-websocket</module>
     <module>johnzon-jsonb</module>
+    <module>johnzon-json-extras</module>
   </modules>
 
   <dependencies>

http://git-wip-us.apache.org/repos/asf/johnzon/blob/5f5b4090/src/site/markdown/index.md
----------------------------------------------------------------------
diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md
index deb746d..61da87c 100644
--- a/src/site/markdown/index.md
+++ b/src/site/markdown/index.md
@@ -390,3 +390,51 @@ in `MessageDecoder`.
         }
     }
 
+
+
+### JSON-B Extra
+
+<pre class="prettyprint linenums"><![CDATA[
+<dependency>
+  <groupId>org.apache.johnzon</groupId>
+  <artifactId>johnzon-jsonb-extras</artifactId>
+  <version>${johnzon.version}</version>
+</dependency>
+]]></pre>
+
+This module provides some extension to JSON-B.
+
+#### Polymorphism
+
+This extension provides a way to handle polymorphism:
+
+For the deserialization side you have to list the potential children
+on the root class:
+
+    @Polymorphic.JsonChildren({
+            Child1.class,
+            Child2.class
+    })
+    public abstract class Root {
+        public String name;
+    }
+
+Then on children you bind an "id" for each of them (note that if you don't 
give one, the simple name is used):
+
+    @Polymorphic.JsonId("first")
+    public class Child1 extends Root {
+        public String type;
+    }
+
+Finally on the field using the root type (polymorphic type) you can
+bind the corresponding serializer and/or deserializer:
+    
+    public class Wrapper {
+        @JsonbTypeSerializer(Polymorphic.Serializer.class)
+        @JsonbTypeDeserializer(Polymorphic.DeSerializer.class)
+        public Root root;
+    
+        @JsonbTypeSerializer(Polymorphic.Serializer.class)
+        @JsonbTypeDeserializer(Polymorphic.DeSerializer.class)
+        public List<Root> roots;
+    }
\ No newline at end of file

Reply via email to