Struts2のValueStackの構造

# 概要
Struts2ではView-Model間のデータ受け渡しやOGNL式の適用対象としてValueStackを利用している。ValueStackはインターフェースでありStruts2におけるデフォルトの実装はOgnlValueStackである。
ここではOgnlValueStackの構造と利用方法について説明する。

# ValueStackの構造
ValueStackはrootとcontextの2つの領域で構成される。

|名称|型|説明|
|-|-|-|
|root|CompoundRoot|OGNL式に指定したプロパティやメソッド呼び出しの対象となるオブジェクトが格納されるスタック。Actionクラスのインスタンスなどが格納される。スタックなので後から格納された項目から取り出される。|
|context|OgnlContext|HTTPリクエストやセッションなどが格納されるMap。OGNL式からそれらにアクセスする際に使用される。|

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132879/6d96920f-8e2a-d90a-562f-869b773a42de.png)

# ValueStackに格納される値
Action実行時にはValueStackは以下の状態になっている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132879/d5a12674-a12e-5e69-2194-30e02ee0fb36.png)
<details><summary>格納される値の詳細</summary>
<table>
<th>名称</th><th>説明</th>
<tr><th colspan=2>root</th></tr>
<tr>
    <td>Action</td>
    <td>実行されるActionのインスタンス。ActionInvocationによってスタックの先頭に格納される</td>
</tr>
<tr>
    <td>TextProvider</td>
    <td>プロパティファイルからi18nに対応したメッセージの取得を行う。ValueStack作成時に最初に格納される。</td>
</tr>
<tr><th colspan=2>context</th></tr>
<tr>
    <td>VALUE_STACK</td>
    <td>正式なキーはcom.opensymphony.xwork2.util.ValueStack.ValueStack。ValueStack自身</td>
</tr>
<tr>
    <td>CONTAINER</td>
    <td>正式なキーはcom.opensymphony.xwork2.ActionContext.container。StrutsPrepareAndExecuteFilterまたはStrutsExecuteFilterが保持するDIコンテナ</td>
</tr>
<tr>
    <td>PARAMETERS</td>
    <td>正式なキーはcom.opensymphony.xwork2.ActionContext.parameters。HttpServletRequestのgetParameterMap()をラップしてMapインターフェースでアクセスできるようにしたもの</td>
</tr>
<tr>
    <td>SESSION</td>
    <td>正式なキーはcom.opensymphony.xwork2.ActionContext.session。HttpSessionをMapとしてアクセスできるようにラップしたSessionMap</td>
</tr>
<tr>
    <td>APPLICATION</td>
    <td>正式なキーはcom.opensymphony.xwork2.ActionContext.application。ServletContextをMapとしてアクセスできるようにラップしたApplicationMap</td>
</tr>
<tr>
    <td>LOCALE</td>
    <td>正式なキーはcom.opensymphony.xwork2.ActionContext.locale。リクエストから取得したロケール</td>
</tr>
<tr>
    <td>HTTP_REQUEST</td>
    <td>正式なキーはcom.opensymphony.xwork2.dispatcher.HttpServletRequest。HttpServletRequest</td>
</tr>
<tr>
    <td>HTTP_RESPONSE</td>
    <td>正式なキーはcom.opensymphony.xwork2.dispatcher.HttpServletResponse。HttpServletResponse</td>
</tr>
<tr>
    <td>SERVLET_CONTEXT</td>
    <td>正式なキーはcom.opensymphony.xwork2.dispatcher.ServletContext。ServletContext
</td>
</tr>
<tr>
    <td>request</td>
    <td>HttpServletRequestをラップしたRequestMap</td>
</tr>
<tr>
    <td>session</td>
    <td>SESSIONと同じ値。OGNL式で#sessionとして参照する。</td>
</tr>
<tr>
    <td>application</td>
    <td>APPLICATIONと同じ値。OGNL式で#applicationとして参照する。</td>
</tr>
<tr>
    <td>parameters</td>
    <td>PARAMETERSと同じ値。OGNL式で#parametersとして参照する。</td>
</tr>
<tr>
    <td>attr</td>
    <td>OGNL式で#attrとして参照する。contextにPAGE_CONTEXTがあればそこから値を取得し、なければ上記のrequest、session、applicationの順に最初に値が見つかったものを返す。</td>
</tr>
<tr>
    <td>struts.actionMapping</td>
    <td>リクエストに紐づくActionMapping</td>
</tr>
</table>
</details>

Actionやインターセプターで例外が発生した場合、発生した例外を保持したExceptionHolderがpushされて以下の状態になっている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132879/98490e96-23fa-5694-17ee-eb986d625054.png)
例えばエラーページのJSP内で以下を記述するとExceptionHolderクラスのgetExceptionメソッドが呼び出されて画面に例外クラス名とメッセージが表示される。他にもexceptionStackでスタックトレースを表示できる。
```jsp:JSP
<h1>Error</h1>
<s:property value="exception"/>
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132879/78c9c0b5-5517-ef95-9feb-c6f8688b2664.png)
Struts2のいくつかのタグはタグの開始時に属性で指定されたオブジェクトをpushすることでタグ内でその効果を使用できるものがある。
- \<s:iterator>
```jsp:JSP
<s:bean name="org.apache.struts2.example.IteratorExample" var="it">
  <s:param name="day" value="'foo'"/>
  <s:param name="day" value="'bar'"/>
</s:bean>

<table border="0" cellspacing="0" cellpadding="1">
<tr>
  <th>Days of the week</th>
</tr>

<s:iterator value="#it.days" status="rowstatus">
  <tr>
    <s:if test="#rowstatus.odd == true">
      <td style="background: grey"><s:property/></td>
    </s:if>
    <s:else>
      <td><s:property/></td>
    </s:else>
  </tr>
</s:iterator>
</table>
```
上記の\<s:iterator>の内部では以下のように#it.daysの要素1つが順にpushされる。また、status属性の値をKeyとしてIteratorStatusがcontextに格納される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132879/6f18375c-4fe3-0061-4a53-a95f5071aa2c.png)
そのため\<s:property/>と記載するだけで'foo'や'bar'が画面に出力され、#rowstatusでIteratorStatusにアクセスしてループ処理のステータスが取得できる。\<s:property/>のvalue属性を省略した場合、value="top"が指定されたとみなされrootの先頭の値が表示される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132879/db470c2b-9cb1-648d-494f-ba251aebc0ef.png)
- \<s:generator>
```jsp:JSP
<s:generator val="%{'aaa,bbb,ccc,ddd,eee'}" separator=",">
  <s:iterator>
    <s:property /><br/>
  </s:iterator>
</s:generator>
```
上記の\<s:generator>の内部では以下のようにIteratorGeneratorがpushされる。IteratorGenerator自体はイテレータであり、\<s:iterator>で要素に対して繰り返し処理を行うことができる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132879/7e5b5ea1-02f9-ee27-2ab8-098c0232aa9a.png)
先ほどと同じように\<s:iteretor>の中では\<s:property/>と記載するだけで'aaa'や'bbb'が画面に出力される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132879/eee5a305-eb5a-0026-8f23-c006d73be3e5.png)
\<s:sort>、\<s:subset>も同様の原理になっており並び替え、部分集合の作成を行うイテレータを生成してrootにpushする。
- \<s:i18n>
```jsp:JSP
<s:i18n name="myCustomBundle">
  The i18n value for key aaa.bbb.ccc in myCustomBundle is <s:property value="getText('aaa.bbb.ccc')" />
</s:i18n>
```
```properties:myCustomBundle.properties
aaa.bbb.ccc=Hello!!
```
上記の\<s:i18n>の内部ではname属性に指定したプロパティファイルから値を取得するResourceBundleTextProviderがpushされる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132879/776ea802-4833-253c-3b33-88fe96fffba2.png)
そのためタグ内でgetTextメソッドを使用するとmyCustomBundle.propertiesから値が取得される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132879/e1666f90-6807-c48f-bd1c-e6eb88ef572d.png)
- \<s:push>
```jsp:JSP
<s:push value="struts.actionMapping">
  namespace:<s:property value="namespace" /><br/>
  name:<s:property value="name" /><br/>
  method:<s:property value="method" />
</s:push>
```
value属性に指定した値をrootにpushする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132879/693fbed7-a89d-8e0d-680c-ce3a3408bc53.png)
value属性に指定した値はOGNL式として評価されるのでcontextに格納されている値を取得するのに利用できる。contextの値は通常#...で取得するが、ピリオドを含むKeyは取得できないのでPushタグでrootにpushしてやることでアクセスすることができる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132879/3d851c09-8fb9-1e31-d157-38aefd2eeba9.png)
- \<s:bean>
```jsp:JSP
<s:bean name="org.apache.struts2.example.IteratorExample" var="it">
  <s:param name="day" value="'foo'"/>
  <s:param name="day" value="'bar'"/>
</s:bean>
```
\<s:iterator>のところでも記載した\<s:bean>タグも、自身をrootにpushすることでタグ内に記載した\<s:param>の設定対象が自身になるようにしている。pushした自身は\</s:bean>でrootから削除されてしまうのでvar属性を指定することでcontext側に格納し後続で使用できるようにしている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132879/a3605cd9-0481-bec9-deca-60ba2e3df79a.png)

# ValueStackへのアクセス
OGNL式でValueStackのrootにアクセスする方法を以下に示す。
※\<s:property value="簡単に言うとここに指定する値"/>
|式|説明|
|-|-|
|フィールド名／メソッド名|rootに格納されているインスタンスのフィールド名／メソッド名を指定すればそのフィールド／メソッドを持つインスタンスをrootの先頭から順に検索し最初に見つかったものを取得／実行する。ただしStruts2ではtoStringなどObjectクラスのメソッドについてはオーバーライドしなければ呼び出せないように制限されている。|
|root|root自身を返す。次のような配列になる[org.demo.actions.HelloAction@728fc979, com.opensymphony.xwork2.DefaultTextProvider@77783003]|
|#root|rootと同じ|
|top|rootの先頭の要素を返す。\<s:property>タグでvalue属性を指定しなかった場合はtopを指定したとみなされ同じ結果になる。|
|[0].top|rootの先頭の要素。[0]は先頭の要素以降を返却しそのtopを取得するので先頭の要素になる。|
|[1].top|rootの2番目の要素。[1]は2番目の要素以降を返却しそのtopを取得するので2番目の要素になる。|

contextにアクセスする方法は以下のとおり。
|式|説明|
|-|-|
|#application|JSPのアプリケーションオブジェクト|
|#session|セッション|
|#request|リクエスト情報|
|#parameters|リクエストパラメータ|
|#attr|JSPのページコンテキスト。ページコンテキストがなければ上記のrequest、session、applicationの順に最初に値が見つかったものを返す。|

contextには上記以外に"struts.actionMapping"のようにピリオドを含むキーで値が格納されているものがあり、それについては\<s:push>で説明した方法でアクセスすることができる。

# ValueStackのライフサイクル
ValueStackはリクエストの度に生成される。詳細にはStrutsPrepareAndExecuteFilterまたはStrutsPrepareFilterで生成されるPrepareOperationsクラスによりValueStackFactoryを使って生成される。
struts.xmlでChain Result（\<result type="chain">）を使って複数のActionを繋げてページを表示させてもリクエストは1回なのでValueStackは最初に生成されたものが引き継がれていく。Chain Resultの場合、以下のように実行したActionがrootに積まれていく。そのためJSPではすべてのActionのプロパティを利用することができ、chainの組み方によって表示が変わるページを作ることもできる。rootに積まれるActionのプロパティは同じ名称のものがあれば下のActionから上のActionに引き継がれる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132879/69b18f1c-3a86-2a34-b7c5-c48415ccb13f.png)