Dropwizard に学ぶ / Jersey Injection Provider

* [Dropwizard](https://dropwizard.github.io/dropwizard/index.html)
* [Jersey 1.18 User Guide](https://jersey.java.net/documentation/1.18/index.html)

## InjectableProvider

* [com.sun.jersey.spi.inject.InjectableProvider](https://github.com/jersey/jersey-1.x/blob/master/jersey-core/src/main/java/com/sun/jersey/spi/inject/InjectableProvider.java)

[Dropwizard](https://dropwizard.github.io/dropwizard/index.html) で利用されている [Jersey-1.18](https://jersey.java.net/documentation/1.18/index.html) では `InjectableProvider<A extends Annotation, C>` を実装した Provider を登録しておくことで、これらが提供する依存オブジェクトを取得することができます。

* _A_ はインジェクト対象を示すアノテーションを指定: `@Context`, etc.
* _C_ は `java.lang.reflect.Type` または `com.sun.jersey.api.model.Parameter`

以下は [Typesafe Config](https://github.com/typesafehub/config) を提供する `ConfigProvider` の例です。

```java:ConfigProvider.java
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.core.spi.component.ComponentScope;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;

import javax.ws.rs.core.Context;
import javax.ws.rs.ext.Provider;

import java.lang.reflect.Type;

import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;

@Provider
public class ConfigProvider implements InjectableProvider<Context, Type> {
    private final Config config;
    
    public ConfigProvider() {
        config = ConfigFactory.load();
    }
    
    @Override
    public Injectable<Config> getInjectable(
            ComponentContext ic, Context a, Type t) {
        if (!t.equals(Config.class))
            return null;

        return new Injectable<Config>() {
            @Override
            public Config getValue() {
                return config;
            }
        };
    }

    @Override
    public ComponentScope getScope() {
        return ComponentScope.Singleton;
    }
}
```
```application.conf
app { version: "0.1.0" }
```

インジェクト対象に `@Context` アノテーションを指定することで、`ConfigProvider` が提供する `Config` オブジェクトを取得できます。

```java:AppResource.java
@Path("/")
public class AppResource {
    @GET
    @Path("version")
    @Produces(MediaType.TEXT_PLAIN)
    public Response version(@Context Config config) {
        return Response.ok(config.getString("app.version")).build();
    }
}
```
## AbstractHttpContextInjectable

* [com.sun.jersey.server.impl.inject.AbstractHttpContextInjectable](https://github.com/jersey/jersey-1.x/blob/master/jersey-server/src/main/java/com/sun/jersey/server/impl/inject/AbstractHttpContextInjectable.java)

依存オブジェクトとして、リクエストヘッダ `Accept-Languages` から、ユーザの利用言語に応じたメッセージカタログを作成したいとします。以下の `I18n` クラスを例にします。 

```java
import java.util.Locale;
import java.util.ResourceBundle;

public class I18n {
    private static final String NAME = "messages";
    private final ResourceBundle bundle;
    
    private I18n(Locale locale) {
        if (locale == null)
            this.bundle = ResourceBundle.getBundle(NAME);
        else
            this.bundle = ResourceBundle.getBundle(NAME, locale);
    }
    
    public String get(String key) {
        return bundle.getString(key);
    }
    
    public static I18n newInstance() {
        return new I18n(null);
    }
    
    public static I18n newInstance(Locale locale) {
        return new I18n(locale);
    }
}
```
```:message.properties
hello=Hello
```
```:message_ja.properties
hello=\u3053\u3093\u306B\u3061\u306F
```
```java
I18n enMessages = I18n.newInstance(Locale.US);
enMessages.get("hello"); // Hello
I18n jaMessages = I18n.newInstance(Locale.JAPAN);
jaMessages.get("hello"); // こんにちは
```

このようにリクエストに応じた依存オブジェクトを作成したい場合、`Injectable` ではリクエストヘッダを得る事ができません。代わりに jersey-server パッケージの `AbstractHttpContextInjectable` を使う事で、`getValue` の引数 `HttpContext` から、リクエストヘッダを参照することができます。

```java:I18nProvider.java
...
import com.sun.jersey.api.core.HttpContext;
import com.sun.jersey.server.impl.inject.AbstractHttpContextInjectable;

import java.util.List;
import java.util.Locale;

@Provider
public class I18nProvider implements InjectableProvider<Context, Type> {
    @Override
    public Injectable<I18n> getInjectable(
            ComponentContext ic, Context a, Type t) {
        if (!t.equals(I18n.class))
            return null;

        return new AbstractHttpContextInjectable<I18n>() {
            @Override
            public I18n getValue(HttpContext ctx) {
                List<Locale> locales = ctx.getRequest().getAcceptableLanguages();
                if (locales.isEmpty())
                    return I18n.newInstance();
                else
                    return I18n.newInstance(locales.get(0));
            }
        };
    }

    @Override
    public ComponentScope getScope() {
        return ComponentScope.PerRequest;
    }
}
```

リクエストごとにインスタンスを生成する必要があるので、スコープは `CompoentScope.PerRequest` とし、メゾッドインジェクションでのみ取得します。

```java
@GET
@Path("hello")
@Produces(MediaType.TEXT_PLAIN)
public Response hello(@Context I18n messages) {
    return Response.ok(messages.get("hello")).build();
}
```

この方法により、リクエスト情報からメッセージカタログを組み立てる同ロジックを各リソースクラスに散りばめる必要がなくなります。

Dropwizard 公式マニュアルの [Authentication](https://dropwizard.github.io/dropwizard/manual/auth.html) の章で、 `@Auth` アノテーションを用いた認証ユーザの取得方法が紹介されていますが、`BasicAuthProvider` / `OAuthProvider` ともに `AbstractHttpContextInjectable` が使われています。