Jersey 2.14でパラメータの受け取りにOptionalが使えるようになった

Jersey 2.14がリリースされたようです!

で、注目は [JERSEY-2612] です。 この対応のおかげで@QueryParamなどのパラメータをOptionalで定義する事が可能になります。 ただしParamConverterを書く必要はありますが。

ParamConverterってなんやねん!って方は JAX-RSでパラメータの受け取り方をいろいろ試す の後半を読んでくださいませー。

リクエストパラメータをOptionalで受け取るコード例

適当ですがサクッちょとサンプル書きました。

リソースクラスをこちらにも掲載します。 ちょー簡単な例ですが、こんな感じでリソースメソッドの引数にOptionalを使えるようになります。

package example;

import java.util.Optional;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

@Path("hello")
public class HelloWorld {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String say(@QueryParam("name") Optional<String> name) {
        return "Hello, " + name.orElse("world") + "!";
    }
}

注意点としては先ほども書きましたがParamConverter実装クラスとParamConverterProvider実装クラスも 自前で準備しなくてはならない事です。 まあ、一度書いたら使い回せるはずなのでサクッと書いておきましょー。

今までこれが出来なかった理由

@QueryParamなどで注釈された引数へ渡される値はSingleValueExtractorのextractメソッドを 通るんですが、Jersey 2.13までのextractメソッドは「値がnullでなければParamConverterなどで変換、 nullなら@DefaultValueで設定された値を返す」という感じの実装になっていました。

その部分を抜粋します。

@Override
public T extract(MultivaluedMap<String, String> parameters) {
    String v = parameters.getFirst(getName());
    if (v != null) {
        try {
            return fromString(v);
        } catch (WebApplicationException ex) {
            throw ex;
        } catch (ProcessingException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new ExtractorException(ex);
        }
    } else {
        return defaultValue();
    }
}

このロジックが原因でOptionalのParamConverterを書いてもnullが渡ってくるアレっぷりでした。

しかしこのextractメソッドはJersey 2.14で次のように修正されました。

@Override
public T extract(MultivaluedMap<String, String> parameters) {
    String v = parameters.getFirst(getName());
    try {
        return fromString((v == null && isDefaultValueRegistered()) ? getDefaultValueString() : v);
    } catch (WebApplicationException ex) {
        throw ex;
    } catch (ProcessingException ex) {
        throw ex;
    } catch (IllegalArgumentException ex) {
        return defaultValue();
    } catch (Exception ex) {
        throw new ExtractorException(ex);
    }
}

ご覧の通り「値がnullかつデフォルト値が設定されていればデフォルト値を、 そうでなければ値をParamConverterなどに渡す」という風になっています。

これで値がnullの場合でもParamConverterを通るようになり、Optionalへの変換が可能になりました。

まとめ

  • Jerseyでリクエストパラメータなどの受け取りにOptional使えるようになって嬉しい
  • ハイテンションでブログ書いたら文章やばい

そんな感じでー