Skip to content

Commit

Permalink
Ensure that JSON-B's @JsonbTypeSerializer @JsonbTypeDeserializer on f…
Browse files Browse the repository at this point in the history
…ields register for reflection
  • Loading branch information
geoand committed Oct 23, 2019
1 parent 87d70c3 commit 63ba182
Show file tree
Hide file tree
Showing 8 changed files with 330 additions and 1 deletion.
1 change: 1 addition & 0 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ stages:
timeoutInMinutes: 25
modules:
- jackson
- jsonb
- jgit
- kogito
- kubernetes-client
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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<ReflectiveClassBuildItem> reflectiveClass,
BuildProducer<SubstrateResourceBundleBuildItem> resourceBundle,
BuildProducer<ServiceProviderBuildItem> serviceProvider,
BuildProducer<AdditionalBeanBuildItem> additionalBeans) {
BuildProducer<AdditionalBeanBuildItem> additionalBeans,
CombinedIndexBuildItem combinedIndexBuildItem) {
reflectiveClass.produce(new ReflectiveClassBuildItem(false, false,
JsonBindingProvider.class.getName()));

Expand All @@ -55,6 +69,29 @@ void build(BuildProducer<ReflectiveClassBuildItem> 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<ReflectiveClassBuildItem> 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
Expand Down
112 changes: 112 additions & 0 deletions integration-tests/jsonb/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<modelVersion>4.0.0</modelVersion>

<parent>
<artifactId>quarkus-integration-tests-parent</artifactId>
<groupId>io.quarkus</groupId>
<version>999-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>

<artifactId>quarkus-integration-test-jsonb</artifactId>
<name>Quarkus - Integration Tests - JSON-B</name>

<properties>
<maven.compiler.parameters>true</maven.compiler.parameters>
</properties>

<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jsonb</artifactId>
</dependency>

<!-- test dependencies -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

<profiles>
<profile>
<id>native-image-it-main</id>
<activation>
<property>
<name>native</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<systemProperties>
<native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
</systemProperties>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<executions>
<execution>
<id>native-image</id>
<goals>
<goal>native-image</goal>
</goals>
<configuration>
<cleanupServer>true</cleanupServer>
<enableHttpUrlHandler>true</enableHttpUrlHandler>
<!-- Requires Quarkus Graal fork to work, will fail otherwise
<enableRetainedHeapReporting>true</enableRetainedHeapReporting>
<enableCodeSizeReporting>true</enableCodeSizeReporting>
-->
<graalvmHome>${graalvmHome}</graalvmHome>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
Original file line number Diff line number Diff line change
@@ -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<Inner> {

@Override
public Inner deserialize(JsonParser parser, DeserializationContext ctx, Type rtType) {
return new Inner("immutable");
}
}

public static class InnerSerializer implements JsonbSerializer<Inner> {

@Override
public void serialize(Inner obj, JsonGenerator gen, SerializationContext ctx) {
gen.writeStartObject();
gen.write("someValue", "unchangeable");
gen.writeEnd();
}
}

}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.quarkus.it.jsonb;

import io.quarkus.test.junit.SubstrateTest;

@SubstrateTest
public class ModelWithSerializerAndDeserializerOnFieldResourceIT extends ModelWithSerializerAndDeserializerOnFieldResourceTest {

}
Original file line number Diff line number Diff line change
@@ -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"));
}
}
1 change: 1 addition & 0 deletions integration-tests/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
<module>neo4j</module>
<module>mongodb-client</module>
<module>jackson</module>
<module>jsonb</module>
<module>resteasy-jackson</module>
<module>jgit</module>
<module>virtual-http</module>
Expand Down

0 comments on commit 63ba182

Please sign in to comment.