谷本 心 in せろ部屋

はてなダイアリーから引っ越してきました

m:immediate="true"のイベントで発生した例外をハンドリングすると、NullPointerExceptionが発生。

まぁ、とにかく、m:immediate="true"で呼ばれるActionメソッドは、
遷移するだけの、シンプルな形にしとけ! って話。


百聞は一見にしかず。
とりあえず、やり方から。

  1. S2JSF Exampleを動かす。
  2. errorpage/pageTran1.htmlのbuttonにm:immediate="true"属性を追加する。
  3. 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チェックを行なう。


の、いずれかになるのかな?