Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Jackson's JsonPOJOBuilder #3688

Merged
merged 1 commit into from
Aug 31, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ stages:
- template: ci-templates/native-build-steps.yaml
parameters:
modules:
- jackson
- jgit
- kogito
- kubernetes-client
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,69 @@
package io.quarkus.jackson.deployment;

import java.util.Collection;

import javax.inject.Inject;

import org.jboss.jandex.*;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

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.ReflectiveHierarchyBuildItem;

public class JacksonProcessor {

private static final DotName JSON_DESERIALIZE = DotName.createSimple(JsonDeserialize.class.getName());
private static final DotName BUILDER_VOID = DotName.createSimple(Void.class.getName());

@Inject
BuildProducer<ReflectiveClassBuildItem> reflectiveClass;

@Inject
BuildProducer<ReflectiveHierarchyBuildItem> reflectiveHierarchyClass;

@Inject
CombinedIndexBuildItem combinedIndexBuildItem;

@BuildStep
void register(BuildProducer<ReflectiveClassBuildItem> reflectiveClass) {
reflectiveClass.produce(new ReflectiveClassBuildItem(true, false,
void register() {
addReflectiveClass(true, false,
"com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector",
"com.fasterxml.jackson.databind.ser.std.SqlDateSerializer"));
"com.fasterxml.jackson.databind.ser.std.SqlDateSerializer");

IndexView index = combinedIndexBuildItem.getIndex();

// TODO: Here we only check for @JsonDeserialize to detect both Model and Builder
// classes to support @JsonPojoBuilder. The @JsonDeserialize annotiona can
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a typo in annotationa. Also the sentence could possible be expressed in a more clear manner, but it's definitely not something to hold up the PR for :)

// also be used for other scenarios than builder beside adding just the class
// no other scenarios are supported (like when the annotation is place on
// methods).

Collection<AnnotationInstance> pojoBuilderInstances = index.getAnnotations(JSON_DESERIALIZE);
for (AnnotationInstance pojoBuilderInstance : pojoBuilderInstances) {
if (AnnotationTarget.Kind.CLASS.equals(pojoBuilderInstance.target().kind())) {
addReflectiveHierarchyClass(pojoBuilderInstance.target().asClass().name());

AnnotationValue annotationValue = pojoBuilderInstance.value("builder");
if (null != annotationValue && AnnotationValue.Kind.CLASS.equals(annotationValue.kind())) {
DotName builderClassName = annotationValue.asClass().name();
if (!BUILDER_VOID.equals(builderClassName)) {
addReflectiveHierarchyClass(builderClassName);
}
}
}
}
}

private void addReflectiveHierarchyClass(DotName className) {
Type jandexType = Type.create(className, Type.Kind.CLASS);
reflectiveHierarchyClass.produce(new ReflectiveHierarchyBuildItem(jandexType));
}

private void addReflectiveClass(boolean methods, boolean fields, String... className) {
reflectiveClass.produce(new ReflectiveClassBuildItem(methods, fields, className));
}
}
124 changes: 124 additions & 0 deletions integration-tests/jackson/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Quarkus Jackson extension tester

This project verifies Jackson (de-)serialization support in native mode.

This project consists of the following modules:

- model

This module contains the library with a simple models. This model makes use
of Jackson to support (de-)serialization to JSON. Various forms of models
exists. At the time of writing the following models are present/tested:
- Immutable models using a Builder to construct new instances.
- Simple POJO model being registered for reflection.

Unit tests are available to prove JVM based JSON (de-)serialization works
properly.

- service

This module contains a very simple RESTful resources with only a POST method
to POST new models to this service. A simple unit test is included to verify
correct behaviour for both unit and integration test.

The following curl command can be used to send POST request to this service:

```
curl -X POST -H "Content-Type: application/json" \
-d '{"version": 2, "id": "123", "value": "val"}' \
-v localhost:8080/<model-type>
```


## Build

To build the project, run the following command from the project root directory:

```
mvn clean package
```

This build should run correctly showing no errors and no test failures.

For the remainder make the service module your current working directory:

```
cd service
```

## Package JVM

Running a JVM based version of the service can either be done with `quarkus:dev`
or by using the JVM based runner.

- **Using `quarkus:dev`**
```
mvn quarkus:dev
```

- **Using JVM runner**
```
java -jar target/service-999-SNAPSHOT-runner.jar
```

In either case posting new model data like described earlier should result in
a successful `201` response code with the posted message in the body. For example:

```
~$ curl -X POST -H "Content-Type: application/json" -d '{"version": 2, "id": "123", "value": "val"}' -v localhost:8080/model
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /model HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 43
>
* upload completely sent off: 43 out of 43 bytes
< HTTP/1.1 201 Created
< Connection: keep-alive
< Content-Type: application/json
< Content-Length: 38
< Date: Thu, 22 Aug 2019 13:31:48 GMT
<
* Connection #0 to host localhost left intact
{"version":2,"id":"123","value":"val"}
```

## Package Native

Checking proper behaviour can be achieved in the following 2 ways:

- **Integration test**

This scenario requires no additional manual steps besides
executing the following command:

```
mvn integration-test verify -Pnative
```

The application will be started automatically and test
scenario's will run. The output will indicate whether the
test ran successfully or not.

In this scenario it is not possible to post new model data
manually. This can be achieved by using the next scenario.

- **Native runner**

Running the native version of the service manually like:

```
mvn package -Pnative
...
./target/service-999-SNAPSHOT-runner
```

Now the application is running new model data can be posted
like described earlier. This should result in a successful
`201` response code with the posted message in the response
body. Just like the JVM example given previously.
108 changes: 108 additions & 0 deletions integration-tests/jackson/model/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?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/maven-v4_0_0.xsd"
>

<modelVersion>4.0.0</modelVersion>

<parent>
<artifactId>quarkus-integration-test-jackson-parent</artifactId>
<groupId>io.quarkus</groupId>
<version>999-SNAPSHOT</version>
</parent>

<artifactId>quarkus-integration-test-jackson-model</artifactId>

<name>Quarkus - Integration Tests - Jackson - model</name>

<properties>
<jackson.version>2.9.9.20190807</jackson.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>${jackson.version}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-core</artifactId>
</dependency>

<!-- SLF4J -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-parameter-names</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.12.2</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<useSystemClassLoader>false</useSystemClassLoader>
</configuration>
</plugin>
</plugins>
</build>

</project>
Loading