tohokuaikiのチラシの裏

技術的ネタとか。

ConfluenceのREST APIを作ってみる - POST編

GETメソッドの場合は引数を@QueryParamで受け取ればなんてことなかったのだけど、POSTの場合はちょっと違った。

なお、通信はXMLじゃなくてすべてJSONを使った場合。

AtlassianのDocumentでいうとこの辺りが参考になる。使わなかったけど。

Developing a REST Service Plugin - Atlassian Developers

あと、そもそもこの技術はJavaのjerseyを使っているので、とりあえずREST with Java (JAX-RS) using Jersey - Tutorialあたりを参考にしたりしました。

そもそもどういうRequestを作るべきなのか?

Labelを付けるREST APIを見ながら考察した。するつRequest Headerはこんな感じ。

Accept:application/json, text/javascript, */*; q=0.01
Accept-Encoding:gzip, deflate
Accept-Language:ja,en-US;q=0.8,en;q=0.6
Connection:keep-alive
Content-Length:70
Content-Type:application/json
Cookie:JSESSIONID=xxxxxxxxxxxxxxxxxxxx

普通にjQueryで$.ajaxするとContent-Typeが「application/x-www-form-urlencoded」になるので、そこを変更しなければならない。

また、POSTデータを見てみると、Request Payloadになっていて、

[{"name":"ccc","id":1481094538830},{"name":"dddd","id":1481094538831}]

まんま、JSONの形。

ということで、これをjQueryで使うには

var data = {
    foo: 123,
    bar: 456
};
$.ajax({
    url: "{ConfluenceのBASE URL}/rest/restpath/1.0/restclasspath/{pageId}/restmethod.json",
    type: "post",
    contentType:'application/json',
    data: JSON.stringify(data)
}).success(function (data, status, xhr) {

という感じでContentTypeを指定し、送信データをJSON.stringifyしてやる。

受けるConfluence側

atlassian-plugin.xml

にてRESTの指定をする。決める重要な情報は、restのpathのみ。
先ほどのAjaxで受ける場合はrestpathの部分を以下のように指定する。

<rest key="foo-bar-rest" path="/restpath" version="1.0">
    <description>RESTの説明</description>
</rest>

JavaクラスとRESTのクラスパス

クラスの場所はどこでもいいので、@Pathアノテーションに持つクラスを作る。
これは結構不思議というか、じゃあ先ほどatlassian-plugin.xmlのpathで指定したものとの関連性は無いのか?という感じである。

とりあえず、先ほどのAjaxの場合は

@Path("/restclasspath")
public class Foobar {

という感じでClassを作る。

JavaメソッドとRESTのメソッド

先ほどのAjaxのパスの場合だと

    @POST
    @Path("{pageId}/restmethod")
    @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    public Response api(@PathParam("pageId") FoobarModel foobarModel) {

というメソッドを作る。メソッド名はapiとしているけどこれは特に意味は無い。わかりやすいのを付ければ何でもいいのではないかと思う。

メソッドの引数としては、メソッドの@Pathアノテーション内で定義したものを、@PathParamでアノテーションされた引数と、POSTされたJSONデータを受け取るModelクラスを指定する。

POST値を受け取るModelクラス
var data = {
    foo: 123,
    bar: 456
};

を受け取りたい場合はこんな感じ。

@XmlRootElement(name = "foobar")
@XmlAccessorType(XmlAccessType.FIELD)
public class foobarModel {

    @JsonIgnore
    @XmlElement(name = "foo")
    private String foo;

    @XmlElement(name = "bar")
    private String barString;

    public foobarModel() {
    }
}

@JsonIgnoreが付いていると、その要素は無くても大丈夫になる。各プロパティに逐一全部つけるのが面倒な場合はクラスにまとめて

@JsonIgnoreProperties(ignoreUnknown=true)
public class foobarModel {
@JsonIgnoreProperties({"foo","bar"})
public class foobarModel {

としてやる。

もし、JSONが下記のようであったら

var data = {
    foo: 123,
    bar: 456
};

プロパティにJsonアノテーションを付ける。

    @JsonProperty("barStr")
    @XmlElement(name = "bar")
    private String barString;

@JsonPropertyがなくても、@XmlElementのnameで指定していればそれを援用してくれる。

このようなオプションを通してもマッピングできない場合は、common.error.jersey.ThrowableExceptionMapper が投げられる。

レスポンスの設定

レスポンスには、OK/NGのステータスとOKの場合はJSONを付けられるので

return Response.ok(new foobarModel).build();

とすると、foobarModelオブジェクトをJSONにしたものを返却できる。