m:immediate="true"のイベントで発生した例外をハンドリングすると、NullPointerExceptionが発生。
まぁ、とにかく、m:immediate="true"で呼ばれるActionメソッドは、
遷移するだけの、シンプルな形にしとけ! って話。
百聞は一見にしかず。
とりあえず、やり方から。
- S2JSF Exampleを動かす。
- errorpage/pageTran1.htmlのbuttonにm:immediate="true"属性を追加する。
- errorpage/pageTran1.htmlに、forEachサンプルの<table>タグをコピーして持ってくる。
つまり、errorpage/pageTran1.htmlを、こんな風にする。
<html xmlns:m="http://www.seasar.org/maya" m:extends="/WEB-INF/layout/layout.html"> <head> <meta http-equiv="Content-Type" content="text/html; charset=Windows-31j" /> <title>The page transition with exception</title> </head> <body> <span m:inject="f:param" m:name="layoutTitle" m:value="The page transition with exception"/> <span m:inject="s:insert" m:name="body"> <form> <table border="1"> <tr bgcolor="#7777FF"> <th>Key</th> <th>Name</th> <th colspan="2">to ResultPage</th> </tr> <span m:inject="s:forEach" m:items="#{forEachDtoList}" m:var="e" m:varIndex="i"> <tr> <td><span m:value="#{e.key}">111</span></td> <td><span m:value="#{e.name}">aaa</span></td> <td><a href="forEachResult.html" m:action="forEachResult">to ResultPage <span m:inject="f:param" m:name="index" m:value="#{i}"/> </a> </td> <td> <input type="button" m:action="forEachResult" value="to ResultPage" onclick="location.href='forEachResult.html'"> <span m:inject="f:param" m:name="index" m:value="#{i}"/> </input> </td> </tr> </span> </table> <input type="button" m:immediate="true" value="The exception due to action" m:action="#{page1Action.throwException}"/> <input type="button" value="The exception due to initialize action" m:action="pageTran2" onclick="location.href='pageTran2.html'" m:immediate="true"/> </form> </span> </body> </html>
で、左側のボタンを押すと、NullPointerExceptionが発生する。
なぜか。処理の順番に従って書いていくと、、、
- 1. m:immediate="true"なイベントは、Invoke Applicationフェーズではなく、Apply Request Valuesフェーズで実行される。
これはJSFの仕様。javax.faces.component.UICommand#queueEventで実装されてる。
- 2. Apply Request Valuesフェーズで実行したActionにて例外が発生した場合は、ErrorPageManagerでハンドルする。ハンドルできる型だった場合は遷移が行なわれ、context.responseComplete()が呼ばれる。
これもまぁJSFの仕様かな。org.seasar.jsf.application.ActionListenerImpl#processActionで実装されてる。
ちなみに、遷移される時に、FacesContextはreleaseされて、nullが設定される。
- 3. 2.の処理を行なったか否かに関わらず、navigationHandler.handleNavigationが実行され、context.renderResponse()が呼ばれる。
これは微妙。2.の処理を行なった場合には、直後でreturnする方が正しいと思う。
- 4. Apply Request Valuesフェーズ完了後、renderResponseかresponseCompleteが呼ばれていた場合は、org.seasar.jsf.lifecycle.LifecycleImpl#initializeChildrenが実行される。
はい、ここでNullPointerExceptionが発生する。
initializeChildrenでは、コンポーネントツリーの初期化をしているようだけど、
そのコンポーネントツリーにforEachタグがあると、
forEachが展開されて、さらにinitializeChildrenが呼ばれる。
展開する時に、forEachタグのitems属性を参照して、
#{xxxItems}を解釈しようとするんだけど、
ここでFacesContextには既にnullがsetされているから、
NullPointerExceptionが発生してしまう。
という事で、、、S2JSF側の対策としては、
- responseCompleteした後は、initializeChildrenを呼ばずにreturnする。
- forEachタグを見つけた際には、facesContextのNullチェックを行なう。
の、いずれかになるのかな?