From 63ba182eb1ec768684259de3f251e4f501078bd0 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Tue, 22 Oct 2019 16:14:34 +0300 Subject: [PATCH] Ensure that JSON-B's @JsonbTypeSerializer @JsonbTypeDeserializer on fields register for reflection --- azure-pipelines.yml | 1 + .../jsonb/deployment/JsonbProcessor.java | 39 +++++- integration-tests/jsonb/pom.xml | 112 ++++++++++++++++++ ...lWithSerializerAndDeserializerOnField.java | 86 ++++++++++++++ ...SerializerDeserializerOnFieldResource.java | 40 +++++++ ...lizerAndDeserializerOnFieldResourceIT.java | 8 ++ ...zerAndDeserializerOnFieldResourceTest.java | 44 +++++++ integration-tests/pom.xml | 1 + 8 files changed, 330 insertions(+), 1 deletion(-) create mode 100644 integration-tests/jsonb/pom.xml create mode 100644 integration-tests/jsonb/src/main/java/io/quarkus/it/jsonb/ModelWithSerializerAndDeserializerOnField.java create mode 100644 integration-tests/jsonb/src/main/java/io/quarkus/it/jsonb/ModelWithSerializerDeserializerOnFieldResource.java create mode 100644 integration-tests/jsonb/src/test/java/io/quarkus/it/jsonb/ModelWithSerializerAndDeserializerOnFieldResourceIT.java create mode 100644 integration-tests/jsonb/src/test/java/io/quarkus/it/jsonb/ModelWithSerializerAndDeserializerOnFieldResourceTest.java diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 7a5805081f8d0..fc336ece6ddc2 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -284,6 +284,7 @@ stages: timeoutInMinutes: 25 modules: - jackson + - jsonb - jgit - kogito - kubernetes-client diff --git a/extensions/jsonb/deployment/src/main/java/io/quarkus/jsonb/deployment/JsonbProcessor.java b/extensions/jsonb/deployment/src/main/java/io/quarkus/jsonb/deployment/JsonbProcessor.java index 53523ae926cc0..dff3234667a42 100755 --- a/extensions/jsonb/deployment/src/main/java/io/quarkus/jsonb/deployment/JsonbProcessor.java +++ b/extensions/jsonb/deployment/src/main/java/io/quarkus/jsonb/deployment/JsonbProcessor.java @@ -1,5 +1,8 @@ package io.quarkus.jsonb.deployment; +import static org.jboss.jandex.AnnotationTarget.Kind.FIELD; +import static org.jboss.jandex.AnnotationTarget.Kind.METHOD; + import java.util.HashSet; import java.util.List; import java.util.Set; @@ -8,12 +11,18 @@ import javax.inject.Singleton; import javax.json.bind.JsonbConfig; import javax.json.bind.adapter.JsonbAdapter; +import javax.json.bind.annotation.JsonbTypeDeserializer; +import javax.json.bind.annotation.JsonbTypeSerializer; import javax.json.bind.serializer.JsonbDeserializer; import javax.json.bind.serializer.JsonbSerializer; import org.eclipse.yasson.JsonBindingProvider; import org.eclipse.yasson.spi.JsonbComponentInstanceCreator; +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.AnnotationValue; import org.jboss.jandex.DotName; +import org.jboss.jandex.IndexView; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem; @@ -22,6 +31,7 @@ import io.quarkus.arc.processor.BeanInfo; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.substrate.ServiceProviderBuildItem; import io.quarkus.deployment.builditem.substrate.SubstrateResourceBundleBuildItem; @@ -40,11 +50,15 @@ public class JsonbProcessor { static final DotName JSONB_ADAPTER_NAME = DotName.createSimple(JsonbAdapter.class.getName()); + private static final DotName JSONB_TYPE_SERIALIZER = DotName.createSimple(JsonbTypeSerializer.class.getName()); + private static final DotName JSONB_TYPE_DESERIALIZER = DotName.createSimple(JsonbTypeDeserializer.class.getName()); + @BuildStep void build(BuildProducer reflectiveClass, BuildProducer resourceBundle, BuildProducer serviceProvider, - BuildProducer additionalBeans) { + BuildProducer additionalBeans, + CombinedIndexBuildItem combinedIndexBuildItem) { reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, JsonBindingProvider.class.getName())); @@ -55,6 +69,29 @@ void build(BuildProducer reflectiveClass, // this needs to be registered manually since the runtime module is not indexed by Jandex additionalBeans.produce(new AdditionalBeanBuildItem(JsonbProducer.class)); + + IndexView index = combinedIndexBuildItem.getIndex(); + + // handle the various @JsonSerialize cases + for (AnnotationInstance serializeInstance : index.getAnnotations(JSONB_TYPE_SERIALIZER)) { + registerInstance(reflectiveClass, serializeInstance); + } + + // handle the various @JsonDeserialize cases + for (AnnotationInstance deserializeInstance : index.getAnnotations(JSONB_TYPE_DESERIALIZER)) { + registerInstance(reflectiveClass, deserializeInstance); + } + } + + private void registerInstance(BuildProducer reflectiveClass, AnnotationInstance instance) { + AnnotationTarget annotationTarget = instance.target(); + if (FIELD.equals(annotationTarget.kind()) || METHOD.equals(annotationTarget.kind())) { + AnnotationValue value = instance.value(); + if (value != null) { + // the Deserializers are constructed internally by JSON-B using a no-args constructor + reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, value.asClass().toString())); + } + } } @BuildStep diff --git a/integration-tests/jsonb/pom.xml b/integration-tests/jsonb/pom.xml new file mode 100644 index 0000000000000..004d6112b5319 --- /dev/null +++ b/integration-tests/jsonb/pom.xml @@ -0,0 +1,112 @@ + + + 4.0.0 + + + quarkus-integration-tests-parent + io.quarkus + 999-SNAPSHOT + ../ + + + quarkus-integration-test-jsonb + Quarkus - Integration Tests - JSON-B + + + true + + + + + io.quarkus + quarkus-resteasy + + + io.quarkus + quarkus-jsonb + + + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + + + + + native-image-it-main + + + native + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + + + + + + + io.quarkus + quarkus-maven-plugin + + + native-image + + native-image + + + true + true + + ${graalvmHome} + + + + + + + + + \ No newline at end of file diff --git a/integration-tests/jsonb/src/main/java/io/quarkus/it/jsonb/ModelWithSerializerAndDeserializerOnField.java b/integration-tests/jsonb/src/main/java/io/quarkus/it/jsonb/ModelWithSerializerAndDeserializerOnField.java new file mode 100644 index 0000000000000..9e7aef15ad725 --- /dev/null +++ b/integration-tests/jsonb/src/main/java/io/quarkus/it/jsonb/ModelWithSerializerAndDeserializerOnField.java @@ -0,0 +1,86 @@ +package io.quarkus.it.jsonb; + +import java.lang.reflect.Type; + +import javax.json.bind.annotation.JsonbTypeDeserializer; +import javax.json.bind.annotation.JsonbTypeSerializer; +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 io.quarkus.runtime.annotations.RegisterForReflection; + +@RegisterForReflection +public class ModelWithSerializerAndDeserializerOnField { + + private String name; + + @JsonbTypeDeserializer(InnerDeserializer.class) + @JsonbTypeSerializer(InnerSerializer.class) + private Inner inner; + + public ModelWithSerializerAndDeserializerOnField() { + } + + public ModelWithSerializerAndDeserializerOnField(String name, Inner inner) { + this.name = name; + this.inner = inner; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Inner getInner() { + return inner; + } + + public void setInner(Inner inner) { + this.inner = inner; + } + + public static class Inner { + private String someValue; + + public Inner() { + } + + public Inner(String someValue) { + this.someValue = someValue; + } + + public String getSomeValue() { + return someValue; + } + + public void setSomeValue(String someValue) { + this.someValue = someValue; + } + } + + public static class InnerDeserializer implements JsonbDeserializer { + + @Override + public Inner deserialize(JsonParser parser, DeserializationContext ctx, Type rtType) { + return new Inner("immutable"); + } + } + + public static class InnerSerializer implements JsonbSerializer { + + @Override + public void serialize(Inner obj, JsonGenerator gen, SerializationContext ctx) { + gen.writeStartObject(); + gen.write("someValue", "unchangeable"); + gen.writeEnd(); + } + } + +} diff --git a/integration-tests/jsonb/src/main/java/io/quarkus/it/jsonb/ModelWithSerializerDeserializerOnFieldResource.java b/integration-tests/jsonb/src/main/java/io/quarkus/it/jsonb/ModelWithSerializerDeserializerOnFieldResource.java new file mode 100644 index 0000000000000..ec7f0ffc1bfd6 --- /dev/null +++ b/integration-tests/jsonb/src/main/java/io/quarkus/it/jsonb/ModelWithSerializerDeserializerOnFieldResource.java @@ -0,0 +1,40 @@ +package io.quarkus.it.jsonb; + +import java.io.IOException; + +import javax.json.bind.Jsonb; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +@Path("fieldserder") +public class ModelWithSerializerDeserializerOnFieldResource { + + private final Jsonb jsonb; + + public ModelWithSerializerDeserializerOnFieldResource(Jsonb jsonb) { + this.jsonb = jsonb; + } + + @POST + @Produces(MediaType.TEXT_PLAIN) + @Consumes(MediaType.APPLICATION_JSON) + public String post(String body) throws IOException { + ModelWithSerializerAndDeserializerOnField input = jsonb.fromJson(body, + ModelWithSerializerAndDeserializerOnField.class); + return input.getName() + "/" + input.getInner().getSomeValue(); + } + + @GET + @Path("/{name}/{someValue}") + @Produces(MediaType.APPLICATION_JSON) + public String get(@PathParam("name") String name, @PathParam("someValue") String someValue) throws IOException { + ModelWithSerializerAndDeserializerOnField input = new ModelWithSerializerAndDeserializerOnField(name, + new ModelWithSerializerAndDeserializerOnField.Inner(someValue)); + return jsonb.toJson(input); + } +} diff --git a/integration-tests/jsonb/src/test/java/io/quarkus/it/jsonb/ModelWithSerializerAndDeserializerOnFieldResourceIT.java b/integration-tests/jsonb/src/test/java/io/quarkus/it/jsonb/ModelWithSerializerAndDeserializerOnFieldResourceIT.java new file mode 100644 index 0000000000000..3cd6030b0bdce --- /dev/null +++ b/integration-tests/jsonb/src/test/java/io/quarkus/it/jsonb/ModelWithSerializerAndDeserializerOnFieldResourceIT.java @@ -0,0 +1,8 @@ +package io.quarkus.it.jsonb; + +import io.quarkus.test.junit.SubstrateTest; + +@SubstrateTest +public class ModelWithSerializerAndDeserializerOnFieldResourceIT extends ModelWithSerializerAndDeserializerOnFieldResourceTest { + +} diff --git a/integration-tests/jsonb/src/test/java/io/quarkus/it/jsonb/ModelWithSerializerAndDeserializerOnFieldResourceTest.java b/integration-tests/jsonb/src/test/java/io/quarkus/it/jsonb/ModelWithSerializerAndDeserializerOnFieldResourceTest.java new file mode 100644 index 0000000000000..05856fd416733 --- /dev/null +++ b/integration-tests/jsonb/src/test/java/io/quarkus/it/jsonb/ModelWithSerializerAndDeserializerOnFieldResourceTest.java @@ -0,0 +1,44 @@ +package io.quarkus.it.jsonb; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; + +import java.io.IOException; + +import javax.json.bind.Jsonb; +import javax.json.bind.JsonbBuilder; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +public class ModelWithSerializerAndDeserializerOnFieldResourceTest { + + @Test + public void testSerializer() throws IOException { + given() + .contentType("application/json") + .when().get("/fieldserder/tester/whatever") + .then() + .statusCode(200) + .body("name", equalTo("tester")) + .body("inner.someValue", equalTo("unchangeable")); + } + + @Test + public void testDeserializer() throws IOException { + Jsonb jsonb = JsonbBuilder.create(); + + given() + .contentType("application/json") + .body(jsonb.toJson( + new ModelWithSerializerAndDeserializerOnField("tester", + new ModelWithSerializerAndDeserializerOnField.Inner()))) + .when().post("/fieldserder") + .then() + .statusCode(200) + .body(is("tester/immutable")); + } +} diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 177655000ae4c..e0db7a3af1b43 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -66,6 +66,7 @@ neo4j mongodb-client jackson + jsonb resteasy-jackson jgit virtual-http