例外のハンドリング

次に、フレームワークとしてはクライアントモジュールで発生した例外を一括してハンドリングするしくみを検討する。
脳内で候補に挙がったいくつかの方法とそれに対する検討内容を以下に記す。

AOPでtry-catchする

まず、「AOPでtry-catchする」という方法を考える。
AOP使うとカッコよさそうだよねぇ」などと安易に考えていたが、「ハンドリングを設定するのは特定の箇所のみ」且つ「でも、その特定の箇所が結構多い」ということでAOPを適用する場所の設定が結構面倒くさそうである。
AOPにしちゃうと「エラー」→「エラーページの出力」というフローもやり難いし。
なのでAOPは却下。
そもそも、「一括して例外のハンドリングしたい」というのは横断的関心とは違うか。

MyFacesServletのサブクラスを作ってservice()の中でtry-catchする

次に、「MyFacesServletのサブクラスを作ってservice()の中でtry-catchする」という方法を考える。
要は、MyFacesServletのサブクラスをFront Controllerに見立ててイベント汎用な処理を突っ込むということである。
この方法は、非JSFのエラーページが相手の場合にはおそらくそれなりにうまく動くし、フローとしても妥当なものなのだが、いかんせんJSFの範囲外なのである。
「入出力文字のエンコード」や「ブラウザキャッシュの無効化」といったPresentation層での設定はServletやFilterに入れるのが妥当であるが、今回のようにフローの根っこになる処理をServletやFilterに入れてしまうと、Servlet以外の環境にアプリケーションを移行する際に困ってしまう。
これと同様の理由で「Filterを作ってtry-catchする」という方法も却下である。

デフォルトActionListenerでtry-catchする

次に、「デフォルトActionListenerでtry-catchする」という方法を考える。
今度はちゃんとJSFの範囲内である。
しかも、faces-config.xmlのaction-listenerに設定してあげるだけで本来のデフォルトActionListenerと差し替えることができる。
しかし、デフォルトActionListenerは、actionの処理をラップしているが、actionListenerの処理はラップしていない。
一般のEvent/Listenerパターンのように、Eventが発生したらListenerコレクションをイテレートしてEventを通知していくため、デフォルトActionListenerの中で例外をハンドリングしても、コレクション内の別要素であるactionListenerの例外はハンドリングできないのである。
今回はクライアントモジュールで発生した例外を一括してハンドリングすることが目的なので、この方法は却下ということになる。

LifeCycle#execute()でtry-catchする

「じゃあ、JSFの範囲内でクライアントモジュールの例外を一括してハンドリングできるところってどこよ」って言ったときに出てくるのがLifeCycleである。
まぁ、LifeCycleはFacesServletから呼ばれているので、本来ならServletでのハンドリングを検討した次の段階でここに辿り着くべきである。
で、MyFacesのFacesServlet#service()の中では、LifeCycle#execute()とLifeCycle#render()が仲良くならんで記述されている。
今回ハンドリングしたいのはLifeCycle#execute()の中で発生した例外なので、LifeCycleのサブクラスを作ってexecute()の中にハンドリング処理を入れるということでようやく一件落着となる。
具体的には、例外が発生したらcatchして、ViewHandler#createView()でUIViewRootを作成し、FacesContext#renderResponse()とFacesContext#setViewRoot()を呼ぶ、という処理になる。
強制ログアウトさせたいレベルの例外であればFacesContext#getExternalContext().getSession(false)でHttpSessionを取得し、HttpSession#invalidate()でセッションを無効化すればよい(Servletに依存するのが気持ち悪いが、ExternalContext#getSession()の戻り値がObjectであるため仕方がない。抽象的なSessionを用意してくれればいいのに)。

LifeCycle#render()で例外が発生した場合は、web.xmlのerror-pageで単純なエラーページを表示する。
ただ、この場合は強制ログアウトの処理ができない。
どうしても強制ログアウトの処理をしたい場合、単純じゃないエラーページを呼べば良いのだが、レンダリングで失敗してるのに単純じゃないエラーページを表示できるのかどうかが心配なので、ひとまず必要になるまでは単純なエラーページで代用する。