読者です 読者をやめる 読者になる 読者になる

Iruca Log

東京で暮らすWeb系エンジニアが日々感じたこと

Jersey - Programmatic API for Building Reources 機能公式ドキュメント和訳

web

この記事は、Jerseyの "Programmatic API for Building Resources" 機能の公式ドキュメントの独自和訳です。
https://jersey.java.net/documentation/latest/resource-builder.html

自動化、開発高速化が注目されている時代において、「サーバAPIを設定によって追加できる」というこの機能はすごく可能性を秘めているのに、
みんなにあんまり知られてないよね…と個人的に思ったので、
もっと使われることを祈って自分なりに和訳してみました。

使い方解説はまた別記事でしようと思います。



14章. Resourceを作るためのプログラマティックなAPI

目次

14.1. はじめに

通常のJAX-RSに沿ったアプリケーションの開発方法は、@Path アノテーションをつけたResourceクラスを作って、 @GET@POST といったアノテーションをつけた各HTTPメソッド用の関数を作り機能を足していくやり方です。そのやり方は JAX-RS Application, Resources and Sub-Resources. の章を参考にしてください。この通常のやり方に加えて、Jerseyではプログラム的にResourceを構築するAPI (Programmatic API)を提供しています。

たくさんのResourceクラスからなるJAX-RSアプリケーションを想像してみてください。これらのResourceクラスはGET, POSTといった通常のHTTPメソッドを備えています。場合によって、@OPTIONを実装して、構築されたResourceのmeta情報を返却するとも足すと有益かもしれません。
こういうとき理想的には、追加機能として機能を公開したいでしょうし、デプロイするタイミングでこのカスタマイズされたOPTIONメソッドを追加するかどうかは決めたいでしょう。
しかし通常のやり方では、OPTIONメソッドを処理してほしいときだけOPTIONメソッドをJAX-RSランタイムに有効化させるようなことはできません。
これを実現するには、このカスタマイズされたメソッドを公開する前にコードを追加したり削除したりする必要があるでしょう。
そういうときにコードを編集する必要がなくなる方法として、あなたの必要に応じてこのProgrammatic APIが使えるというわけです。

もうひとつのProgrammatic APIの用途としては、たくさんの設定パラメータによって変化する、汎用的なRestfulインタフェースを作るときです。たとえばデータベースの値によってAPI処理を定義するような形です。あなたのResourceクラス達は異なるメソッド、異なる構造が、新しいアプリケーションを公開するたびに必要になります。そういうとき、Resourceクラス達をJavaのbyte codeの操作によって構築する方法を見てきたでしょう。しかしこういったとき、ここで紹介するProgrammatic Resource作成APIを使ってこの問題を綺麗に解くことができるのです。もっと詳しく、このAPIの利用方法についてみてみましょう。


14.2 Programmatic APIHello World

JerseyのProgrammatic APIJAX-RSのResourceモデルを完全サポートするように設計されています。言い換えるならば、通常のJAX-RSの実装方法でアノテーションを利用して作られたResourceクラス達は、ここで紹介するProgrammatic APIを使っても必ず実装できるということです。単純なHello Worldを返すResourceクラスについて、通常の実装方法と、そして次にJersey Programmatic Resource APIを使った方法についてみてみましょう。

次に示す例は、通常のJAX-RSHello World Resourceクラスです。

例 14.1. 通常のResourceクラス

@Path("helloworld")
public class HelloWorldResource {
 
    @GET
    @Produces("text/plain")
    public String getHello() {
        return "Hello World!";
    }
}

これはHTTP GETメソッドで"Hello World!"という文字列を返す単純なResourceクラスです。text/plainのmedia typeでシリアライズされています。

さて、次にProgrammatic APIを使用して設計した単純なResourceクラスを見てみましょう。

例 14.2 ProgrammaticなResource

package org.glassfish.jersey.examples.helloworld;
 
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Response;
import org.glassfish.jersey.process.Inflector;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.model.Resource;
import org.glassfish.jersey.server.model.ResourceMethod;
 
 
public static class MyResourceConfig extends ResourceConfig {
 
    public MyResourceConfig() {
        final Resource.Builder resourceBuilder = Resource.builder();
        resourceBuilder.path("helloworld");
 
        final ResourceMethod.Builder methodBuilder = resourceBuilder.addMethod("GET");
        methodBuilder.produces(MediaType.TEXT_PLAIN_TYPE)
                .handledBy(new Inflector<ContainerRequestContext, String>() {
 
            @Override
            public String apply(ContainerRequestContext containerRequestContext) {
                return "Hello World!";
            }
        });
 
        final Resource resource = resourceBuilder.build();
        registerResources(resource);
    }
}

まず、上の例のMyResourceConfigクラスのコンストラクタを見てください。JerseyのProgrammatic Resourceモデルは、ResourceMethodsを含むResourcesによって作成されます。コード例の中では、"Hello World"を返却するGETメソッドを含むResourceクラスから単一のリソースが作成されるでしょう。

JerseyにおいてProgrammaticにリソースを作る主な導入の仕方はResource.Builderクラスです。Resource.Builderクラスはコード例の中で使われているpathメソッドを含むごく少数のメソッドを含んでいます。ちなみにpathメソッドはPath名をセットするメソッドです。もうひとつの有用なメソッドは、addMethod(String path) です。これはResource.Builderに新しいメソッドを追加し、別の新しいメソッドを続けて追加できるようにBuilder自身を返すメソッドです。

Resourceのmethod builderは複数のメソッドを含んでいて、それぞれリクエストに受け入れる・レスポンスとして生成するmedia typeのセットや、name bindingの定義や、非同期実行する際のタイムアウト秒のセットなどが行われます。上記の例で"Hello World!"を返却する関数で示されるように、Resource methodを何かしら処理する関数は常に必要です。Resource methodの処理のされ方については、他にもいろいろな形があります。コード例の中では、Inflectorの実装が使われています。JerseyのInflectorインタフェースには、リクエストをレスポンスに変換するための単純なルールが定義されています。
1つのInflectorではResponseを返してもよいですし、コード例で示されるようにレスポンスに該当するEntity Objectを直接返却してもかまいません。

もうひとつのやり方は、handledBy(Class handlerClass, Method method)関数を使ってJavaのmethod handlerを用意して、内包するクラスと一緒に、選択されたjava.lang.reflect.Methodインスタンスにそれを受け渡す方法です。

Resource methodモデルの作成はresource method builderにbuild()関数を割り込ませることで明示的に完了できます。親リソースが一度buildされると自動的に子Resource methodモデルもbuildされるので、それらには必要がないともいえます。Resourceモデルがbuildされたら、コード例のMyResourceConfigコンストラクタの最後の行のようにResource Configにregisterします。


14.2.1 Programmatic Resourceの公開

さて、次はどうやってProgrammatic Resourceをデプロイするかを見てみましょう。最も簡単にProgrammatic Resourceを登録し、あなたのApplicationをセットアップする方法は、JerseyのResource Configクラスをユーティリティとして使うやりかたです。このクラスはApplication Classの拡張です。もしあなたがJerseyアプリケーションをサーブレットコンテナとしてデプロイするなら、Configurationとして用いられるApplicationクラスのサブクラスを提供する必要があるでしょう。そのためにあなたはorg.glassfish.jersey.servlet.ServletContainerのServlet entryを定義して、あなたのデプロイしたいJAX-RSアプリケーションの初期パラメータとなるjavax.ws.rs.Applicationクラス名を指定したweb.xmlを使うかもしれません。

上記の例では、このアプリケーションというのはMyResourceConfigクラスにあたります。javax.ws.rs.Applicationクラスの拡張であるResourceConfigクラスを拡張させた形でMyResourceConfigクラスがあるのは、こういった理由からです。

次のコード例は、ResourceConfig JAX-RS アプリケーションをデプロイするために使われるweb.xmlの一部です。

例 14.3 ProgrammaticなResource

...
<servlet>
    <servlet-name>org.glassfish.jersey.examples.helloworld.MyApplication</servlet-name>
    <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
    <init-param>
        <param-name>javax.ws.rs.Application</param-name>
        <param-value>org.glassfish.jersey.examples.helloworld.MyResourceConfig</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>org.glassfish.jersey.examples.helloworld.MyApplication</servlet-name>
    <url-pattern>/*</url-pattern>
</servlet-mapping>
...


もしあなたが他のデプロイ方法がよくて、他からアクセス可能な設定用のResourceConfigクラスを持っていれば、直接ResourceConfigクラスからregisterResources()メソッドを呼んでProgrammatic Resourcesを登録することができます。その場合は、その前に登録していたResource達はregisterResources()メソッドによって置き換えられてしまうことに注意してください。
Jersey Programmatic APIは標準的なJAX-RSの機能ではないので、ResourceConfigは上記に書かれたように必ずProgrammatic Resource達をregisterする必要があります。詳しくはdeployment の章を参照してください。


14.3 そのほかの使用例

例14.4

final Resource.Builder resourceBuilder = Resource.builder(HelloWorldResource.class);
resourceBuilder.addMethod("OPTIONS")
    .handledBy(new Inflector<ContainerRequestContext, Response>() {
        @Override
        public Response apply(ContainerRequestContext containerRequestContext) {
            return Response.ok("This is a response to an OPTIONS method.").build();
        }
    });
final Resource resource = resourceBuilder.build();

上記の例の中では、Resourceは1つのHelloWorldResourceというResourceクラスから作られています。こうして作られたResourceクラスは1つのGETメソッドが実装されており、'text/plain'のmedia typeで "Hello World!"という文字列Entityを生成します。既存のJAX-RS Resourceクラス達を一度調べてみて、ここで紹介しているBuilder APIを使ってこうしたResourceクラス達を改善できるかもしれないというのはとても重要なことです。上記の例の中では、すでに実装されたHelloWorldResourceクラス達をOPTIONSメソッドを追加することで改善してみました。OPTIONSメソッドはResponseを返却するInflectorによって処理されています。

次の例では、さらに子PathにResourceをどのように定義するかについてみていきます。

例 14.5

final Resource.Builder resourceBuilder = Resource.builder("helloworld");
 
final Resource.Builder childResource = resourceBuilder.addChildResource("subresource");
childResource.addMethod("GET").handledBy(new GetInflector());
 
final Resource resource = resourceBuilder.build();

さらにsub-pathにあるResource Methodたちは、いわゆる子Resourceのモデルです。子Resourceのモデル(単純に子Resourceと呼びましょうか)は他のProgrammatic Resource達と同じように作られますが、親Resourceの子としてregisterされます。例の中では子Resrouceは親BuilderからaddChildResource(String path)メソッドを使って直接作られています。

しかし、子Resourceのモデルを標準的なResourceとして別々に作り、それを子Resourceとして選択した親Resourceに追加することもできます。これはつまり、子Resourceは異なる親Resourceに再利用しながら定義できるということです(単一の子Resourceを作っておいて、それを複数の異なる親Resourceにregisterできるということです)。JAX-RSには孫Resourceという考え方は無いので、それぞれの子Resourceが更に子Resourceを持つことはできません。
たとえば、"/root/" がリソースへのpathであり、 "sub/resource/method" が子Resourceのpathであるとき、"sub", "resource", "method" からそれぞれ更にネストされた別々の子Resourceを作ることはできないということです。

もっと詳しい情報が必要な人は、resource builder APIjavadocを参照してください。


14.4 Model処理

Jerseyを使うとモデル処理providerと呼ばれるものをregisterすることもできます。このproviderを使って、アプリケーションのデプロイの際にResource Modelを強化/修正することができます。
これは上級者向けの機能で、ほとんどのケースでは必要とされないでしょう。しかし、たとえばこの章の冒頭で説明したようなresourceへのOPTIONSメソッドをそれぞれの公開されたResourceに追加するような場合を想像してみてください。Programmatic Resourceと通常のJAX-RS Resourceを含めてすべてのResourceクラスにこれを適用したいとします。この適用をするためには、まずはじめにorg.glassfish.jersey.server.model.ModelProcessorを拡張したクラスをregisterする必要があります。

モデル処理の実装例はこちら。

例 14.6

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Configuration;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
 
import org.glassfish.jersey.process.Inflector;
import org.glassfish.jersey.server.model.ModelProcessor;
import org.glassfish.jersey.server.model.Resource;
import org.glassfish.jersey.server.model.ResourceMethod;
import org.glassfish.jersey.server.model.ResourceModel;
 
@Provider
public static class MyOptionsModelProcessor implements ModelProcessor {
 
    @Override
    public ResourceModel processResourceModel(ResourceModel resourceModel, Configuration configuration) {
        // we get the resource model and we want to enhance each resource by OPTIONS method
        ResourceModel.Builder newResourceModelBuilder = new ResourceModel.Builder(false);
        for (final Resource resource : resourceModel.getResources()) {
            // for each resource in the resource model we create a new builder which is based on the old resource
            final Resource.Builder resourceBuilder = Resource.builder(resource);
 
            // we add a new OPTIONS method to each resource
            // note that we should check whether the method does not yet exist to avoid failures
            resourceBuilder.addMethod("OPTIONS")
                .handledBy(new Inflector<ContainerRequestContext, String>() {
                    @Override
                    public String apply(ContainerRequestContext containerRequestContext) {
                        return "On this path the resource with " + resource.getResourceMethods().size()
                            + " methods is deployed.";
                    }
                    }).produces(MediaType.TEXT_PLAIN)
                      .extended(true); // extended means: not part of core RESTful API
 
            // we add to the model new resource which is a combination of the old resource enhanced
            // by the OPTIONS method
            newResourceModelBuilder.addResource(resourceBuilder.build());
        }
 
        final ResourceModel newResourceModel = newResourceModelBuilder.build();
        // and we return new model
        return newResourceModel;
    };
 
    @Override
    public ResourceModel processSubResource(ResourceModel subResourceModel, Configuration configuration) {
        // we just return the original subResourceModel which means we do not want to do any modification
        return subResourceModel;
    }
}

例に示しているMyOptionsModelProcessorは、registerされているResourceすべてを見てまわる単純なモデルです。新しいOPTIONSメソッドを追加されている以外は、新しいResourceは元のResourceと同じです。
他の通常のJAX-RSの拡張providerを追加するのと同じ方法で、カスタマイズしたModelProcessorをresigsterしさえすればよいです。アプリケーションをデプロイするときに、JerseyはすべてのregisterされたModel Processorを探して実行して回ります。このように、ModelProcessorはResourceモデルに対してどんな操作でもできる強力な方法です。更に、たとえば古いResource Modelを完全に無視して"Hello World!"を返すResource Modelを返却することもできます。結果として"Hello World!"を返すResourceだけがアプリケーション上にデプロイされることになります。

もちろんこういった例に大きな意味は無いことは分かっています。
しかし、こういったシナリオを通してModel Processorのコンセプトが以下に強力なものかを示したいのです。より良い現実的なユースケースとしては、たとえば、それぞれのアプリケーションにおいてデプロイされているアプリに関するメタデータを返すカスタマイズされたResourceを常に追加するというようなものです。
もしくは、特定のResourceやResource methodをフィルタリングしたいとき。これもModel Processorが役立つ状況の一つです。

また、processSubResource(ResourceModel subResourceModel, Configuration configuration)メソッドはそれぞれの子resourceに対して実行されることに注意してください。この例は単純で何の操作もしていませんが、たとえばすべての子Resourceに対してOPTIONSメソッド処理ハンドラを強化することもできます。

すべてのModel Processorは常に、有効なResource Modelを返すということを覚えておいてください。すでに気づいているかもしれませんが、上記の例ではこの重要なルールについてがカバーされていません。もともとのResource Modelの中に既にOPTIONSメソッド処理が定義されているResourceがあった場合、別のOPTIONSメソッド処理を追加しようとすると、アプリケーションをデプロイする際にResource ModelのValidationの際にのエラーを引き起こします。最終的なResource Modelの整合性を保つために、Modelprocessorは上記の例に示すよりもきちんと、頑健に作られるべきでしょう。