”JAXRStoRAML CLI”を利用してJAXRS→RAMLの変換を試してみました！

# はじめに
　現在、MuleSoftの導入をするプロジェクトに参画しておりまして、API管理基盤としてMuleSoftを導入する際に、既存API資産をMuleSoftの基盤上で再利用するためにAnypoint Exchangeに追加する要件があります。
　API仕様の追加は、サポートするAPI仕様のファイル（RAML）をAnypoint Exchangeに登録することで追加できます。
　課題は、API仕様のファイルを持たない既存APIは、サードパティーツールなど何らかの方法でAPI仕様を作成する必要があり、実現性や出力できる情報を検証し、APIの追加方法を設計する必要があります。
　今回の目的、MuleSoftも開発に参加しているRAML生成ツール（“JAXRS To RAML CLI”）を利用して、実現性や出力できる情報を検証しましたので仕様や制約につて説明します。

# JAXRSToRAML CLIとは
　ツールの概要をまとめました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76270/df2231c8-10a7-08c6-b047-7c6b1e324e0b.png)

 [JAXRSToRAML CLIへのリンク](https://mvnrepository.com/artifact/org.raml.jaxrs/jaxrs-to-raml-cli)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76270/8dba4f16-7d16-23bd-5a42-a7560df06b64.png)

# RMAL生成ツールの変換の仕組み
ツールを実行すると、自動で各アノテーションに対応するRAMLの要素に変換されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76270/33a6c4c7-e640-96a2-db70-1115a58da80f.png)

# 出力できる情報
　ツールで出力できる内容をまとめました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/76270/b9033d94-0d8f-0cb1-d18e-68ed28c358eb.png)

# 検証ソース
　下記のソースで出力できる情報をまとめました。

``` APIクラス.java
    @Path("getWithDto")
    @GET
    @Produces("application/json")
    public ProducedJsonValue getWithDto(
    		@CookieParam("sessionid") @DefaultValue("999") int sid,
    		@HeaderParam("user-agent") @DefaultValue("XXXXXX") String userAgent, 
    		ConsumedValue consumed) {
      return null;
    }
    
    @GET
    @Path("getById/{id}")
    @Produces("application/json")
    @Examples({
    	@Example(useCase = "name", value = "tarou")
    })
    public Response getById(
    		@CookieParam("sessionid") @DefaultValue("999") int sid,
    		@HeaderParam("user-agent") @DefaultValue("XXXXXX") String userAgent, 
    		@PathParam("id") String id,
    		@QueryParam("from") @DefaultValue("10") int from,
    		List<String> orderBy) {

       return Response.status(400).entity("getUserById is called, id : " + id).build();
    }
            
    @Path("postWithDto")
    @POST
    @Consumes("application/json")
    @Produces("application/json")
    public ProducedValue postWithDto(ConsumedValue consumed) {
      return null;
    }
    
    @Path("postFile")
    @POST
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Produces(MediaType.APPLICATION_JSON)
    @Examples({
    	@Example(useCase = "name", value = "tarou")
    })
    public String postFile(
    		@CookieParam("sessionid") @DefaultValue("999") int sid,
    		@HeaderParam("user-agent") @DefaultValue("XXXXXX") String userAgent, 
           @FormDataParam("appBundle") InputStream appBundleStream,
           @FormDataParam("appBundle") FormDataContentDisposition appFileDetail,
           @FormDataParam("configFile") InputStream configFileStream,
           @FormDataParam("configFile") FormDataContentDisposition configFileDetail,
           @FormDataParam("consumed") ConsumedValue2 consumedValue
        ) {
    	
      return null;
    }
    
    @POST
    @Path("postFormData")
    @Produces("application/json")
    @Examples({
    	@Example(useCase = "name", value = "tarou")
    })
    public Response postFormData(
    		@CookieParam("sessionid") @DefaultValue("999") int sid,
    		@HeaderParam("user-agent") @DefaultValue("XXXXXX") String userAgent, 
    		@FormParam("name") @DefaultValue("XXXXXX") String name,
    		@FormParam("age") @DefaultValue("999") int age) {

    	if (age == 2) {
    		return Response.status(200).entity("addUser is called, name : " + name + ", age : " + age).build();
    	}
        return Response.status(500).entity("addUser is called, name : " + name + ", age : " + age).build();
    }
        
    @DELETE
    @Path("delete/{id}")
    @Produces("application/json")
    @Example("{name : 'head exsample!' }")
    public Boolean delete(
    		@CookieParam("sessionid") @DefaultValue("999") int sid,
    		@HeaderParam("user-agent") @DefaultValue("XXXXXX") String userAgent, 
    		@PathParam("id") int id,
    		@QueryParam("name") @DefaultValue("default tarou") String name) {
    	//return Response.status(403).entity("Server Error").build();
    	return null;
    }
    
    @HEAD
    @Produces("application/json")
    @Path("head/{id}")
    public ConsumedValue head(
    		@CookieParam("sessionid") @DefaultValue("999") int sid,
    		@HeaderParam("user-agent") @DefaultValue("XXXXXX") String userAgent, 
    		@PathParam("id") String id, 
    		@QueryParam("name") @DefaultValue("default tarou") String name) throws Exception {
    	//return Response.status(403).entity("Server Error").build();
    	return null;
    }
    
    @PUT
    @Path("put/{id}")
    @Produces("application/json")
    @Examples({
        @Example(useCase = "name", value = "tarou"),
        @Example(useCase = "email", value = "xxx@xxx.com")
    })
    public Response put(
    		@CookieParam("sessionid") @DefaultValue("999") int sid,
    		@HeaderParam("user-agent") @DefaultValue("XXXXXX") String userAgent, 
    		ConsumedValue value, @PathParam("id") String id,
    		@QueryParam("name") @DefaultValue("default tarou") String name) {
    	return Response.status(404).entity("Not Found").build();
    }
    
    @PATCH
    @Path("patch/{id}")
    @Produces("application/json")
    @Examples({
    	@Example(useCase = "name", value = "tarou")
    })
    public Response patch(
    		@CookieParam("sessionid") @DefaultValue("999") int sid,
    		@HeaderParam("user-agent") @DefaultValue("XXXXXX") String userAgent, 
    		@PathParam("id") String id,
    		@QueryParam("name") @DefaultValue("default tarou") String name) {
    	return Response.status(202).entity("Not Found").build();
    }
```


# 制限事項
　上記検証の結果、いくつか制限事項が見つかりましたので説明します。

- Getメソッドに引数にDTOを設定している場合、リクエストのボディーとして認識されクエリパラメータとして認識されない。GetメソッドはURLのパラメータで値を渡すので、リクエストのボディー情報で出力されるとExchangeのAPI仕様のゴミ情報となってしまいます。
- HTTPメソッドの引数のDTOにアノテーションを設定している場合、リクエスト情報として認識されずRAMLに出力されない。業務アプリケーションでは単項目チェックのアノテーションを付けることが多く外すことは難しいと考えています。
- 戻り値に汎用的なResponseクラスを設定している場合、カスタムタイプとしてオブジェクト構造が読み込まれないため、Exchangeに表示できない。
- @Producesのアノテーションを付けないとレスポンス情報が出力されない。


# まとめ
　検証の結果、ツールの仕様に合わせてAPIを構築している場合、自動で出力できる情報が多く有益なツールだと感じました。
　ただし、既存の業務アプリケーションの場合、セキュリティ機能を拡張する独自のアノテーションや制限事項に上げた実装をしている場合が多く、実装を変更することが難しいことから、実装に合わせて他の選択肢を検討する必要があると感じました。
　ただ、このようなAPI仕様を出力するライブラリは非常に少ないので今後の機能拡充で様々な実装にも対応できると既存APIの再利用が高まりそうですね！！
　では