Payara micro触る(楽しようとしたらハマった話)

buildはgradle
普段使ってるのはmavenなので
よく分からんのでこの辺はコピペ
opengl氏の記事多め。ありがたや~)

uberJarを作る方法はこちらから~。
qiita.com

下記記事のソースを参考にしつつ書いていきます。
qiita.com

とりあえず以下のようなソースを書いて動かしてみます。

// Resourceクラス
@Path("/example")
public class Resource {
  @GET
  @Path("hello")
  public String hello(){
    return "test";
  }
  @GET
  @Path("user")
  @Produces(MediaType.APPLICATION_JSON)
  public User get(){
    return new User("xx");
  }
}

//Applicationクラス
@ApplicationPath("/api")
public class MyApplication extends Application {
}

//Userクラス(ただのDTO)
@Value
@AllArgsConstructor
public class User{
  String name;
}

とりあえずここではuberJarにして
java -jar ROOT.jar
で実行しました。

http://localhost:8080/api/example/hello
にアクセスすると想定通り"test"の文字が表示されます。

http://localhost:8080/api/example/user
にアクセスするとコンソールにMessageBodyWriterが見つからない、と言ってエラーが出ます。


lombokによって吐かれているファイルを見てみると以下のようになっています。
(Decompiler PluginによってDecompileした物なので少し違う部分があるかもしれません)

public final class User{
  private final String name;

  public String getName() {
    return this.name;
  }

  public boolean equals(Object o) {
    if (o == this) {
      return true;
    }
    else if (!(o instanceof User)) {
      return false;
    }
    else {
      User other = (User) o;
      String this$name = this.getName();
      String other$name = other.getName();
      if (this$name == null) {
        if (other$name != null) {
          return false;
        }
      }
      else if (!this$name.equals(other$name)) {
        return false;
      }

      return true;
    }
  }

  public int hashCode() {
    boolean PRIME = true;
    byte result = 1;
    String $name = this.getName();
    int result1 = result * 59 + ($name == null ? 43 : $name.hashCode());
    return result1;
  }

  public String toString() {

    return "User(name=" + this.getName() + ")";
  }

  @ConstructorProperties({
    "name"
  })
  public User(String name) {
    this.name = name;
  }
}

するとConstructorPropertiesなるAnnotationが付与されており
以下のissueを見てみると、jackson 2.7でサポートされているように見えます。

github.com

以下の記事を見るとJackson 2.7で、この機能が追加された際に
JsonPropertyのアノテーションとConstructorPropertiesのフィールド名が競合した際の場合のようです。
少し今回やりたいこととは違います。

【解決済み】jackson 2.7 以後は @ConstructorProperties を参照するようになっているので、lombok を使っている場合に何も考えずにアップグレードすると死ぬ - tokuhirom's blog

一応、最新のPayaraのリリースを見ると内部ではJackson 2.8が使われているとのことなので
このConstructorPropertiesの機能は使えるはずです。
github.com

とりあえずWorkaroundとして解決案2つと分からなかった解決案1つを書いておきます。

解決方法1

@Dataアノテーションを使う

@Dataアノテーションを使うと古典的なPOJOが生成されるので
使い方としては

Prisoner prisoner=new Prisoner();
prisoner.setName("hogehoge");

といったような形になります。

ソースを追記して、書いてみます。

// 新しくクラスを追加
// Accessorsはmethod chainするために追加してます。
@Data
@Accessors(chain=true)
public class Prisoner {
  private String name;
}
// Resourceクラスに追加
  @GET
  @Path("prisoner")
  @Produces(MediaType.APPLICATION_JSON)
  public Prisoner getPrisoner(){
    return new Prisoner().setName("orekyuu");
  }


http://localhost:8080/api/example/prisoner
にアクセスすると
{"name":"orekyuu"}
がちゃんと返却されます。

解決方法2

MessageBodyWriterを自分で書いてお茶を濁す。

@javax.ws.rs.ext.Provider
@Produces(MediaType.APPLICATION_JSON)
public class Provider implements MessageBodyWriter<User> {
  ObjectMapper mapper=new ObjectMapper();
  @Override
  public long getSize(User user, Class<?> clazz, Type type, Annotation[] annotations, MediaType mediaType) {
    // TODO Auto-generated method stub
    return -1;
  }

  @Override
  public boolean isWriteable(Class<?> clazz, Type type, Annotation[] annotations, MediaType mediaType) {
    // TODO Auto-generated method stub
    return clazz.isAssignableFrom(User.class);
  }

  @Override
  public void writeTo(User user, Class<?> clazz, Type type, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> multivaluedMap, OutputStream outputStream)
    throws IOException, WebApplicationException {
    mapper.writeValue(outputStream, user);
  }
}

こちらは
http://localhost:8080/api/example/userにアクセスすると
エラーを吐かずに想定通り、JSONで返却されました。

解決方法3

内部で使われてるJacksonの動きを変える。

こちらはいくつか内部のソースを追ってみましたが、分からず諦めました。

おわり

今日のところはこんなところで。

リポジトリはこちらに置いておきます。

github.com