Jersey Grizzly で始める JAX-RS 入門 〜STEP4〜


前回までで作成したアプリケーションを Customer エンティティの CRUD 操作をできるようにしていきます。

CustomerResource の変更

前回作成した CustomerResource は以下の内容でした。

@Path("customers")
public class CustomerResource {

    @GET
    @Path("{id}")
    @Produces({MediaType.APPLICATION_JSON})
    public Customer get(@PathParam("id") Long id) {
        return Ebean.find(Customer.class, id);
    }
    
    @POST
    @Consumes({MediaType.APPLICATION_JSON})
    public Response createCustomer(Customer customer) {
        Ebean.save(customer);
        return Response.created(
                URI.create("/customers/" + customer.getId())).build();
    }

}

この CustomerResource に更新と削除のメソッドを追加していきます。

更新は HTTP PUT にて受け付けることにします。

    @PUT
    @Path("{id}")
    @Consumes({MediaType.APPLICATION_JSON})
    public Response update(Customer customer) {

        Customer current = Ebean.find(Customer.class, customer.getId());

        if (current == null)
            throw new WebApplicationException(Response.Status.NOT_FOUND);

        current.setFirstName(customer.getFirstName());
        current.setLastName(customer.getLastName());
        current.setAddress(customer.getAddress());
        Ebean.update(current);

        return Response.noContent().build();
    }

PUT にてリソースが更新された場合 Response.ok() で 200、メッセージボディにメッセージを含めることもできますが、ここでは 204 を返却するようにしました。

続いて同様に削除処理は HTTP DELETE にて受け付けます。

    @DELETE
    @Path("{id}")
    public Response delete(@PathParam("id") Long id) {

        int count = Ebean.delete(Customer.class, id);

        if (count != 1)
            throw new WebApplicationException(Response.Status.NOT_FOUND);

        return Response.noContent().build();

    }

こちらも同様で 204 を返却するようにしています。

静的コンテンツのハンドラ追加

コマンドラインから curl でリクエストするのではなく、HTML 画面からリクエストできるようにしていきます。

Grizzly に HttpHandler を追加して、静的コンテンツを扱えるようにしましょう。 CLStaticHttpHandler という出来合いのハンドラがあるため、静的コンテンツの格納場所とパスを指定して登録します。

    public static void main(String[] args) throws IOException {

        setupEbean();

        final HttpServer server =
                GrizzlyHttpServerFactory.createHttpServer(
                        BASE_URI,
                        ResourceConfig.forApplicationClass(RsResourceConfig.class), false);

        // static assets
        server.getServerConfiguration().addHttpHandler(
                new CLStaticHttpHandler(Main.class.getClassLoader(), "/assets/"), "/static");

        server.start();
        Runtime.getRuntime().addShutdownHook(new Thread(server::shutdownNow));

        openBrowser();

        log.info("Start grizzly. Press any key to exit.");
        System.in.read();
        System.exit(0);

    }

クラスローダが見つけられる /assets/ 配下を静的コンテンツの格納場所として指定しています。 また、リスエストは /static というパスをこのハンドラの対象として指定しています。

静的コンテンツとして resources/assets/index.html を作成しましょう。

Bootstrap の雛形として以下とします。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
    <title>Bootstrap 101 Template</title>

    <!-- Bootstrap -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
    <link href="main.css" rel="stylesheet">

    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
    <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
    <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->
</head>
<body>

<div class="container">

    <h2>Hello</h2>

</div>

<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>

</body>
</html>

同じ場所に main.css を作ります。

.container {
  width: auto;
  padding: 20px;
}

リクエスト処理

作成した resources/assets/index.html の container 以下にリクエスト発行処理を埋めていきます。 JSはライブラリ使わずに vanilla で行きます。

最初は POST 処理です。

    <h2>Create Customer</h2>
    <div class="row">
        <div class="col-xs-8">
            <textarea id="idPostRequestResult" class="form-control" rows="3">{"firstName":"Michael","lastName":" Stipe","address":{"street":"1016","city":"Hollywood","state":"CA","zip":"90038","country":"US"}}</textarea>
        </div>
    </div>
    <div class="row">
        <div class="col-xs-8">
            <button type="button" id="idPostRequestButton" class="btn btn-default">POST</button>
            <font id="idPostResCode" color="red"></font>
        </div>
    </div>
    <script>
        document.getElementById("idPostRequestButton").addEventListener("click", function(){
            var request = new XMLHttpRequest();
            request.open('POST', '/customers', true);
            request.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
            request.onload = function() {
                document.getElementById("idPostResCode").textContent =
                    request.status + "[" + request.getResponseHeader("Location")+ "]";
            };
            request.send(document.getElementById("idPostRequestResult").value);
        });
    </script>

テキストエリアのJSONを POST リクエストするようにしています。

続いて GET と PUT 処理。

    <h2>Get Customer & Put Customer</h2>
    <div class="row">
        <div class="col-xs-2">
            <input type="text" id="idGetPutRequestId" class="form-control" value="1" placeholder="ID">
        </div>
        <div class="col-xs-8">
            <button type="button" id="idGetRequestButton" class="btn btn-default">GET</button>
            <button type="button" id="idPutRequestButton" class="btn btn-default">PUT</button>
            <font id="idGetPutResCode" color="red"></font>
        </div>
    </div>
    <div class="row">
        <div class="col-xs-8">
            <textarea id="idGetRequestResult" class="form-control" rows="3"></textarea>
        </div>
    </div>
    <script>
        document.getElementById("idGetRequestButton").addEventListener("click", function(){
            var request = new XMLHttpRequest();
            var path = '/customers/' + document.getElementById("idGetPutRequestId").value
            request.open('GET', path, true);
            request.onload = function() {
                document.getElementById("idGetPutResCode").textContent = request.status;
                if (request.status >= 200 && request.status < 400) {
                    document.getElementById("idGetRequestResult").value = request.responseText;
                }
            };
            request.send();
        });

        document.getElementById("idPutRequestButton").addEventListener("click", function(){
            var request = new XMLHttpRequest();
            var path = '/customers/' + document.getElementById("idGetPutRequestId").value
            request.open('PUT', path, true);
            request.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
            request.onload = function() {
                document.getElementById("idGetPutResCode").textContent = request.status;
            };
            request.send(document.getElementById("idGetRequestResult").value);
        });
    </script>

最後に DELETE 処理。

    <h2>Delete Customer</h2>
    <div class="row">
        <div class="col-xs-2">
            <input type="text" id="idDeleteRequestId" class="form-control" value="1" placeholder="ID">
        </div>
        <div class="col-xs-8">
            <button type="button" id="idDeleteRequestButton" class="btn btn-default">GET</button>
            <font id="idDeleteResCode" color="red"></font>
        </div>
    </div>
    <script>
        document.getElementById("idDeleteRequestButton").addEventListener("click", function(){
            var request = new XMLHttpRequest();
            var path = '/customers/' + document.getElementById("idDeleteRequestId").value
            request.open('DELETE', path, true);
            request.onload = function() {
                document.getElementById("idDeleteResCode").textContent = request.status;
            };
            request.send();
        });
    </script>

いずれもボタン押下のイベントで XMLHttpRequest でリクエスト飛ばしているだけです。

実行

前回までの Index リソースにアクセスした場合に、作成した index.html にリダイレクトするように処理を変更します。

@Path("/")
public class Index {

    private static Logger log = LoggerFactory.getLogger(Index.class);

    @GET
    public Response hello() {
        return Response.seeOther(
                UriBuilder.fromPath("/static/index.html").build()
        ).build();
    }

}

では実行してみましょう。

./gradlew run

f:id:Naotsugu:20160330215216p:plain

POST ボタン押下すると 201 が返却され登録が行われているようです。

f:id:Naotsugu:20160330215258p:plain

GET ボタン押下すると id が採番されて登録した内容が取得できています。

f:id:Naotsugu:20160330215320p:plain

GETで取得したJSONを編集して PUT ボタン押下すると 204 となり更新が行われました。

f:id:Naotsugu:20160330215518p:plain

ボタン名が間違っていますが、最後に id を指定して DELETEすると 204となり削除が行われます。

f:id:Naotsugu:20160330215524p:plain

以上簡単ですが、JAX-RS からの CRUD 操作を見てみました。

ここまでのソースはGithubを参照してください。