OGNLとs:pushの組み合わせでハッピーStruts2ライフ

Struts2でWebアプリを作る際に、JSPを部品化したいと思って以下のような「ユーザー表示」テンプレートを作ったとする。

<table>
  <tr><td>お名前</td><td><s:property value="user.name" /></td></tr>
  <tr><td>メールアドレス</td><td><s:property value="user.mail" /></td></tr>
</table>

これを表示するときに、うまくAction側で user というプロパティ名にできればいいんだけど、同じページにユーザーをふたり表示させたくて

public class MyAction {
  private User user1;
  private User user2;
  public User getUser1() { return user1; }
  public User getUser2() { return user2; }
  ...
}

としただけで、このテンプレートはそのまま使えなくなる。

user なんて名前を含めるからいけないんじゃんということで

<table>
  <tr><td>お名前</td><td><s:property value="name" /></td></tr>
  <tr><td>メールアドレス</td><td><s:property value="mail" /></td></tr>
</table>

として、テンプレートを使う側で

<s:push value="user1">
<jsp:include page="user.jsp" />
</s:push>
<s:push value="user2">
<jsp:include page="user.jsp" />
</s:push>

とするといちおうはOKだけど、元々のテンプレートでは user が null でもナアナアで済ませて(s:property の出力が全て空になるだけ)くれたのに対して、こちらの場合は user1 や user2 が null だと s:push が問答無用でエラーを吐いてしまう。OGNL的にはnullがいくらあっても問題ないけど、ValueStack自体にnullを積むのはダメ絶対!というわけだ。

これに対する簡単で柔軟性も維持できる解決策としては、こんなのしか思いつかなかった。

<s:push value="#{'user':user1}">
<jsp:include page="user.jsp" />
</s:push>
<s:push value="#{'user':user2}">
<jsp:include page="user.jsp" />
</s:push>

見ての通り、userというキーを持つMapをその場でValueStackに積んでいる。テンプレートは最初の user という名前で参照する方を使う。これなら user1 や user2 が null でも問題ない。

この手法は他にも利点があって、たとえば user 以外にも何かテンプレートに対してパラメータを渡したいような場合、Mapに追加のキーを値を設定すればお手軽に設定できる。

<table>
  <tr><td>お名前</td><td><s:property value="user.name" /></td></tr>
  <tr><td>メールアドレス</td><td><s:property value="user.mail" /></td></tr>
  <s:if test="showAge"><tr><td>年齢</td><td><s:property value="user.age" /></td></tr></s:if>
</table>
<s:push value="#{'user':user1, 'showAge':false}">
<jsp:include page="user.jsp" />
</s:push>
<s:push value="#{'user':user2, 'showAge':true}">
<jsp:include page="user.jsp" />
</s:push>

OGNLって気持ち悪いけど、一旦魂を売り渡してしまうと楽しくなってくるね。