Open LibertyのJAX-RS実装がJSONを扱う際のライブラリを確認する

# What's？

Open LibertyのJakarta RESTful Web Services（JAX-RS）のリファレンスを見ていて、JSONをどうやって処理しているのかがわからなかったので確認してみようかなと。

[Jakarta RESTful Web Services](https://openliberty.io/docs/24.0.0.10/reference/feature/restfulWS-3.1.html)

結果としては、Jakarta JSON Binding（JSON-B）でした。

[Jakarta JSON Binding
](https://openliberty.io/docs/24.0.0.10/reference/feature/jsonb-3.0.html)

# 環境

```shell
$ java --version
openjdk 21.0.4 2024-07-16
OpenJDK Runtime Environment (build 21.0.4+7-Ubuntu-1ubuntu222.04)
OpenJDK 64-Bit Server VM (build 21.0.4+7-Ubuntu-1ubuntu222.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.9.9 (8e8579a9e76f7d015ee5ec7bfcdc97d260186937)
Maven home: /home/charon/.sdkman/candidates/maven/current
Java version: 21.0.4, vendor: Ubuntu, runtime: /usr/lib/jvm/java-21-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.15.0-124-generic", arch: "amd64", family: "unix"
```

# Open Libertyプロジェクトを作成する

Get started（Create a starter application）を使って、Open LibertyのMavenプロジェクトを作成。

```shell
$ curl -G https://start.openliberty.io/api/start \
  -o jaxrs-sample.zip \
  -d a=jaxrs-sample \
  -d b=maven \
  -d e=10.0 \
  -d g=com.example \
  -d j=21 \
  -d m=6.1
```

zipを展開。

```shell
$ unzip -d jaxrs-sample jaxrs-sample.zip
Archive:  jaxrs-sample.zip
   creating: jaxrs-sample/src/main/java/com/example/
  inflating: jaxrs-sample/src/main/liberty/config/server.xml
  inflating: jaxrs-sample/Dockerfile
  inflating: jaxrs-sample/.dockerignore
  inflating: jaxrs-sample/src/main/java/com/example/rest/RestApplication.java
  inflating: jaxrs-sample/README.txt
  inflating: jaxrs-sample/.mvn/wrapper/maven-wrapper.jar
  inflating: jaxrs-sample/.mvn/wrapper/maven-wrapper.properties
  inflating: jaxrs-sample/mvnw
  inflating: jaxrs-sample/mvnw.cmd
  inflating: jaxrs-sample/pom.xml
  inflating: jaxrs-sample/.gitignore
  inflating: jaxrs-sample/src/main/resources/META-INF/microprofile-config.properties
```

`pom.xml`と`server.xml`を少し修正して、JAX-RSソースコードを作成。

`Application`のサブクラスは、`@ApplicationPath`のパスだけ修正しました。

```java:src/main/java/com/example/rest/RestApplication.java
package com.example.rest;

import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;

@ApplicationPath("/")
public class RestApplication extends Application {

}
```

JSONを使ったリクエスト、レスポンスに対応するクラス。

```java:src/main/java/com/example/rest/CalcRequest.java
package com.example.rest;

public class CalcRequest {
    private Integer x;
    private Integer y;

    public CalcRequest() {
        System.out.println("CalcRequest@constructor");
        Thread.dumpStack();
    }

    public Integer getX() {
        return x;
    }

    public void setX(Integer x) {
        System.out.println("CalcRequest@setY");
        Thread.dumpStack();
        this.x = x;
    }

    public Integer getY() {
        return y;
    }

    public void setY(Integer y) {
        this.y = y;
    }
}
```

```java:src/main/java/com/example/rest/CalcResponse.java
package com.example.rest;

public class CalcResponse {
    private Integer result;

    public CalcResponse(Integer result) {
        this.result = result;
    }

    public Integer getResult() {
        System.out.println("CalcResponse@getX");
        Thread.dumpStack();
        return result;
    }

    public void setResult(Integer result) {
        this.result = result;
    }
}
```

呼び出し確認ができるように、`Thread#dumpStack`を仕込んでいます。

JAX-RSリソースクラス。

```java:src/main/java/com/example/rest/CalcResource.java
package com.example.rest;

import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

@Path("/calc")
public class CalcResource {
    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public CalcResponse calc(CalcRequest request) {
        return new CalcResponse(request.getX() + request.getY());
    }
}
```

### JSONを処理しているライブラリを確認する

ビルド、起動。

```shell
$ mvn compile liberty:dev
```

確認。

```shell
$ curl -X POST -H 'Content-Type: application/json' localhost:9080/calc -d '{"x": 10, "y": 15}'
{"result":25}
```

この時に得られたスタックトレース。

```shell
[INFO] CalcRequest@constructor
[INFO] [err] java.lang.Exception: Stack trace
[INFO] [err]    at java.base/java.lang.Thread.dumpStack(Thread.java:2209)
[INFO] [err]    at com.example.rest.CalcRequest.<init>(CalcRequest.java:9)
[INFO] [err]    at java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:62)
[INFO] [err]    at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:502)
[INFO] [err]    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:486)
[INFO] [err]    at org.eclipse.yasson.internal.ReflectionUtils.createNoArgConstructorInstance(ReflectionUtils.java:282)
[INFO] [err]    at [internal classes]
[INFO] [err]    at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:413)
[INFO] [err]    at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:385)
[INFO] [err]    at [internal classes]
[INFO] [err]    at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:240)
[INFO] [err]    at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:154)
[INFO] [err]    at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:385)
[INFO] [err]    at [internal classes]
[INFO] [err]    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:614)
[INFO] [err]    at com.ibm.ws.webcontainer.servlet.ServletWrapper.service(ServletWrapper.java:1266)
[INFO] [err]    at [internal classes]
[INFO] [err]    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
[INFO] [err]    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
[INFO] [err]    at java.base/java.lang.Thread.run(Thread.java:1583)
[INFO] CalcRequest@setY
[INFO] [err] java.lang.Exception: Stack trace
[INFO] [err]    at java.base/java.lang.Thread.dumpStack(Thread.java:2209)
[INFO] [err]    at com.example.rest.CalcRequest.setX(CalcRequest.java:18)
[INFO] [err]    at org.eclipse.yasson.internal.deserializer.ValueSetterDeserializer.deserialize(ValueSetterDeserializer.java:37)
[INFO] [err]    at [internal classes]
[INFO] [err]    at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:413)
[INFO] [err]    at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:385)
[INFO] [err]    at [internal classes]
[INFO] [err]    at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:240)
[INFO] [err]    at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:154)
[INFO] [err]    at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:385)
[INFO] [err]    at [internal classes]
[INFO] [err]    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:614)
[INFO] [err]    at com.ibm.ws.webcontainer.servlet.ServletWrapper.service(ServletWrapper.java:1266)
[INFO] [err]    at [internal classes]
[INFO] [err]    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
[INFO] [err]    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
[INFO] [err]    at java.base/java.lang.Thread.run(Thread.java:1583)
[INFO] CalcResponse@getX
[INFO] [err] java.lang.Exception: Stack trace
[INFO] [err]    at java.base/java.lang.Thread.dumpStack(Thread.java:2209)
[INFO] [err]    at com.example.rest.CalcResponse.getResult(CalcResponse.java:12)
[INFO] [err]    at org.eclipse.yasson.internal.serializer.ValueGetterSerializer.serialize(ValueGetterSerializer.java:39)
[INFO] [err]    at org.eclipse.yasson.internal.serializer.ObjectSerializer.lambda$serialize$0(ObjectSerializer.java:41)
[INFO] [err]    at java.base/java.util.LinkedHashMap.forEach(LinkedHashMap.java:986)
[INFO] [err]    at org.eclipse.yasson.internal.serializer.ObjectSerializer.serialize(ObjectSerializer.java:38)
[INFO] [err]    at [internal classes]
[INFO] [err]    at org.jboss.resteasy.core.interception.jaxrs.ServerWriterInterceptorContext.lambda$writeTo$1(ServerWriterInterceptorContext.java:74)
[INFO] [err]    at org.jboss.resteasy.plugins.server.servlet.Servlet3AsyncHttpRequest$Servlet3ExecutionContext.executeBlockingIo(Servlet3AsyncHttpRequest.java:392)
[INFO] [err]    at [internal classes]
[INFO] [err]    at org.jboss.resteasy.core.interception.jaxrs.ServerWriterInterceptorContext.lambda$getStarted$0(ServerWriterInterceptorContext.java:68)
[INFO] [err]    at org.jboss.resteasy.core.interception.jaxrs.ServerWriterInterceptorContext.aroundWriteTo(ServerWriterInterceptorContext.java:87)
[INFO] [err]    at [internal classes]
[INFO] [err]    at org.jboss.resteasy.core.ServerResponseWriter.lambda$writeNomapResponse$3(ServerResponseWriter.java:216)
[INFO] [err]    at org.jboss.resteasy.core.interception.jaxrs.ContainerResponseContextImpl.filter(ContainerResponseContextImpl.java:420)
[INFO] [err]    at [internal classes]
[INFO] [err]    at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:240)
[INFO] [err]    at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:154)
[INFO] [err]    at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:385)
[INFO] [err]    at [internal classes]
[INFO] [err]    at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:614)
[INFO] [err]    at com.ibm.ws.webcontainer.servlet.ServletWrapper.service(ServletWrapper.java:1266)
[INFO] [err]    at [internal classes]
[INFO] [err]    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
[INFO] [err]    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
[INFO] [err]    at java.base/java.lang.Thread.run(Thread.java:1583)
```

Eclipse Yassonなので、JSON-Bですね。

なお、使用するフィーチャーを絞り込む場合は、`restfulWS-x.y`だけではなく`jsonb-x.y`も必要です。

```xml
    <featureManager>
        <feature>restfulWS-3.1</feature>
        <feature>jsonb-3.0</feature>
    </featureManager>
```

`jsonb-x.y`がない場合は、JSONのリクエストを送信しても受け付けてもらえません

```shell
[INFO] RESTEASY003200: Could not find message body reader for type: class com.example.rest.CalcRequest of content type: application/json
```

# pom.xmlとserver.xmlの変更箇所

このあたりを少し変更しました。

```shell
$ diff -u pom.xml*
--- pom.xml     2024-10-18 12:44:34.376505859 +0900
+++ pom.xml.org 2024-10-18 03:34:46.000000000 +0900
@@ -6,14 +6,13 @@

     <groupId>com.example</groupId>
     <artifactId>jaxrs-sample</artifactId>
-    <version>0.0.1-SNAPSHOT</version>
+    <version>1.0-SNAPSHOT</version>
     <packaging>war</packaging>

     <properties>
         <maven.compiler.source>21</maven.compiler.source>
         <maven.compiler.target>21</maven.compiler.target>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
     </properties>

     <dependencies>
@@ -52,15 +51,6 @@
             <plugin>
                 <groupId>io.openliberty.tools</groupId>
                 <artifactId>liberty-maven-plugin</artifactId>
-                <version>3.11.1</version>
-                <configuration>
-                  <runtimeArtifact>
-                    <groupId>io.openliberty</groupId>
-                    <artifactId>openliberty-kernel</artifactId>
-                    <version>24.0.0.10</version>
-                    <type>zip</type>
-                  </runtimeArtifact>
-                </configuration>
             </plugin>
         </plugins>
     </build>
```

```shell
$ diff -u src/main/liberty/config/server.xml*
--- src/main/liberty/config/server.xml  2024-10-18 12:44:28.900481784 +0900
+++ src/main/liberty/config/server.xml.org      2024-10-18 03:34:46.000000000 +0900
@@ -35,7 +35,7 @@
     <applicationManager autoExpand="true"/>

     <!-- Configures the application on a specified context root -->
-    <webApplication contextRoot="/" location="jaxrs-sample.war" />
+    <webApplication contextRoot="/jaxrs-sample" location="jaxrs-sample.war" />

     <!-- Default SSL configuration enables trust for default certificates from the Java runtime -->
     <ssl id="defaultSSLConfig" trustDefaultCerts="true" />
```