遷移先ページの動的な変更

Strutsを使ったシステムで、Actionのオブジェクトは各種処理を実施したあと、遷移先の画面を決定することになる。Actionは遷移先を通知する手段としてActionMapping#findForward(String)を使う。
この仕掛けのおかげでクラスの責務の分離が取れているように思う。

  • 表示内容(遷移先の実体)は forwardタグで書く。jspやvelocity,tilesの識別子など。
  • Actionはフローにおいて処理のみを担当し、その結果だけを知らせる.具体的な遷移先は指示しない。
    • 「結果」と「遷移先」をひも付けるのが ActionMapping#findForward(String)
    • よくあるのはsuccessとか、errorとかで統一するような手法. これだとActionは"成功"を伝えるのみで、成功のときにどう遷移するのかはActionはまったく関与していない

実際に ActionMapping#findForward("success") とすると 一つのActionForwardが選ばれる。ただ、こういう要件も想定しうる。

  • Actionの処理結果が"success"のとき、
    • UserAgentが IEの場合は a.jsp
    • UserAgentが DoCoMoの携帯の場合は b.jsp
    • UserAgentが w3mの場合は c.jsp
  • 表示したい

なにかいい方法がないかを考えてみたのだが思いつかない。モジュールなどを使う方法があるが、これだとconfiguration全体を半分コピーするようなことになってしまう。
"success"を通知するのはActionの仕事だけど、それがUserAgentに依存したりして表示内容を動的に変える仕組み自体はActionにとっては関係がない状態を維持したいと思う(少なくとも私は)。

単純に、


  


  
  

という風にしてしまうと、Action側でfindForwardの引数に適切なものをセットすることになり、上の分離は実現できない。
これを解決できそうな方法としてActionMappingクラスのインプリメンタを変更するというのを思いついたが、これは実際にはうまくいかないようだ。

  
    
      
      
      ..
    
  

この方法を用いても MyActionMappingのインスタンスに対してrequestやresponse,sessionを通知してもらうインタフェースがない*1

解決方法としては、ActionMappingインスタンスを取得してActionに渡すのはRequestProcessorの仕事なので、RequestProcessorを拡張するという方法がある。
具体的には processMapping(request,response,path) をオーバーライドしてActionMappingをラッピングしたオブジェクトを返すというもの。

  1. 元々の processMapping()の処理に従い ActionMappingを取得する。
  2. new ActionMappingWrapper(request, response, 元々のactionMapping) とかして、これをactionに渡してしまう
  3. Action側で mapping.findForward("success") すると…
  4. ActionMappingWrapperのfindForward()にやってくるので、ここでは request,responseを見ながら元々のactionMapping#findForward( string+"_ie5" ) とかする
  5. Action側は何も考えないでrequest,responseに依存した振り分けが可能に!

ただし、この方法は 1リクエストごとに ActionMappingWrapperがnew()されてしまいコストが気になるかもしれない(サイト横断的に実施する場合はサンクコストだが)。で、S2Strutsがあるとこんな方法も使える。S2StrutsはActionのインスタンスの管理をS2のもとに実施している。ということは、Actionクラスに対してinterceptorを挟むことができる。

  1. S2RequestProcessor が interceptorの挟まったactionのexecute()をコールする
  2. interceptorにやってくる。interceptorはexecute()の全ての引数を取得できる。
  3. 引数のActionMappingを取得する。
  4. new ActionMappingWrapper(request, response, 元々のactionMapping) とかして、これをactionに渡してしまう
  5. Action側で mapping.findForward("success") する
  6. ActionMappingWrapperのfindForward()にやってくるので、ここでは request,responseを見ながら元々のactionMapping#findForward( string+"_ie5" ) とかする
  7. Action側は何も考えないでrequest,responseに依存した振り分けが可能に!

というような感じ。ただ、リクエストごとに wrapper がnewされるのでコストが高いかもしれない。

この問題の解決に、のようにしておいて、あくまでビュー側で処理するというのも賢いかも。SiteMeshとかはUserAgentごとに表示物を変える仕掛けもあったような気がする。しかし、ただそれだけのためにビューに使うフレームワーク/コンポーネントを選ぶ/変えるというのも…

いまはこの方法で結構うまく回っている(工数が少なくて、影響個所を限定できて、Actionの独立度は維持できている)。けど、もっといい方法があったら知りたい…なにかありませんか。

*1:標準のRequestProcessorでは ActionMappingを ModuleConfigあたりから拾ってきているが、これはActionMappingのインスタンスを取得するためで、ModuleConfigやActionMappingに対してリクエストなどを通知するインタフェースは存在していない