アクション合成

前回の記事ではラムダ式使った共通処理の記述をやったけど、 Playの公式ドキュメント読んでたらフツーに アクション合成 なるものがありましたね。

この機能をいじってみたので使い方メモを残す。

他にもGlobalオブジェクトを作ることで全アクション共通の処理が書けるけど、 特定ページだけ認証したりとかやるならアクション合成を使う。

Actionクラス

合成したいアクションはplay.mvc.Actionクラスを継承して書く。 サンプルは公式ドキュメントのものとほぼ同じ。

public class CommonAction extends Action.Simple {
  @Override
  public Promise<Result> call(Context ctx) throws Throwable {
    someOperation();
    return delegate.call(ctx);
  }
}

Http.Context型の引数ctxからはリクエストに関する情報が取れたり、 ctx.argsにマッピングを追加して後続のアクションに情報を伝えたりできる。

合成元のアクションを実行するにはdelegate.call(ctx)を呼び出す。

Withアノテーションによるアクション合成

一番基本的な合成方法。

@With(CommonAction)
public Result index() {
  return ok("Ok");
}

これでsomeOperation()が実行された後にOKレスポンスが返る。

自作アノテーションによるアクション合成

Withアノテーションは複数個書けない。

@With(HogeAction)
@With(FugaAction)  /* NG */
public Result index() {...}

WithにRepeatableアノテーションがないからなんだけど、 アクションが1個しか合成できないのは不便極まりない。 複数のアクションを合成するには自作アノテーションを使う。

@With(HogeAction)
public @interface HogeActionAnnot {}

@With(FugaAction)
public @interface FugaActionAnnot {}

@HogeActionAnnot
@FugaActionAnnot
public Result index() {...}

パラメータ付きアノテーション

アノテーションのパラメータはconfigurationから取得できる。

@With(PiyoAction)
public @interface PiyoActionAnnot {
  public String value();
}

public class PiyoAction extends Action<PiyoActionAnnot> {
  @Override
  public Promise<Result> call(Context ctx) throws Throwable {
    System.out.println(configuration.value());
    return delegate.call(ctx);
  }
}

Repeatableアノテーションを使う時の注意

例えば以下のように同じ自作アノテーションを複数個使いたい場合。

@PiyoActionAnnot(value = "piyo")
@PiyoActionAnnot(value = "piyopiyo")
public Result index() {...}

PiyoActionAnnotにはRepeatableアノテーションを付ける。 引数にはPiyoActionAnnotの配列をパラメータに持つアノテーションを指定する。

@With(PiyoAction)
public @interface PiyoActionAnnotHolder {
  public PiyoActionAnnot[] value();
}

@Repeatable(value = PiyoActionAnnotHolder.class)
public @interface PiyoActionAnnot {
  public String value();
}

あれ、Holderの方にWithアノテーション?と思うかもしれないけど、 index()に2個ついてるPiyoActionAnnot、実態はHolder。 なのでPiyoAction側でアノテーションを使う場合も以下のように書く。

public class PiyoAction extends Action<PiyoActionAnnotHolder> {
  @Override
  public Promise<Result> call(Context ctx) throws Throwable {
    for (PiyoActionAnnot annot : configuration.value()) {
      System.out.println(annot.value());  // PiyoActionAnnotの引数が出力される
    }
    return delegate.call(ctx);
  }
}