システムエラーページをケータイとパソコンで分ける

SAStruts話。

ケータイとパソコンでシステムエラーページを変えたいそうだ。ケータイとPCでアクションは別なのだが、困った事にサービスクラスを共用している。このサービスクラス、内部でS2JDBCがSQLRuntimeExceptionを投げる可能性があるのだが、この例外をアクションでcatchしてエラーページに遷移といった処理を書くのは面倒くさいし、RuntimeExcetionなので記述モレもありえる。そもそも、そんな処理はフレームワークがやるべき仕事だ。

SAStrutsStrutsなのでglobal-exceptionが使えるが、これは例外の型を見て遷移先を指定するもののようなのでユーザエージェント等の条件ではシステムエラーページを分ける事ができない。ならばアスペクトを使って別の例外に変換すればいいのでは?と思ってやってみたら簡単にできた。

ThrowsInterceptorを使ったケータイとパソコンのシステムエラーページの出し分け。

まずインターセプタのクラス、SMART Deploy規約に従って作るのが無難

public class DeviceThrowsInterceptor extends ThrowsInterceptor {
    
    /** インターセプタにもHttpServletRequestをバインディングできる */
    public HttpServletRequest request;

    /** requestでケータイかどうか判別 */
    private boolean isMobileAccess() {
        String ua = request.getHeader("User-Agent");

        // ...何か処理、ua等でケータイかどうか判別

        return true;
        
    }

    public void handleThrowable(Throwable e, MethodInvocation invocation) throws Throwable {
        if (this.isMobileAccess()) {
            throw new MobileException(e);
        } else {
            throw new PCException(e);
        }
    }
}

customizer.diconを修正。作成したインターセプタの設定をactionCustomizerの最初に追加する。

  <component name="actionCustomizer" class="org.seasar.framework.container.customizer.CustomizerChain">
    <initMethod name="addAspectCustomizer">
      <arg>"deviceThrowsInterceptor"</arg>
    </initMethod>
    <initMethod name="addAspectCustomizer">
      <arg>"aop.traceInterceptor"</arg>
    </initMethod>
    ...
  </component>

struts-config.xmlのglobal-exceptionに遷移先を記述

 <global-exceptions>
    <exception type="jp.paulownia.sastruts.exception.MobileException" path="/error/mobile.jsp" key="mobileException"/>
    <exception type="jp.paulownia.sastruts.exception.PCException" path="/error/pc.jsp" key="pcException"/>
 </global-exceptions>

ThrowsInterceptorが投げるMobileExceptionやPCExceptionはRuntimeExceptionの方が良い。アスペクトが検査例外を投げたとき、Actionのメソッドにその例外がthrows宣言されていないとUndeclaredThrowableExceptionが発生してしまう。

追記

Cool Deployの場合、HttpServletRequestがバインディングされない。

http://d.hatena.ne.jp/n-kizashi/20080627

http://s2container.seasar.org/2.4/ja/DIContainer.html#Customizerインスタンス属性がsingleton以外のインターセプタを利用する
を参考にして、useLookupAdapterをtrueにしてください。

これをやらないとダメっぽです

customizer.dicon

  <component name="actionCustomizer" class="org.seasar.framework.container.customizer.CustomizerChain">
    <initMethod name="addCustomizer">
      <arg>
        <component class="org.seasar.framework.container.customizer.AspectCustomizer">
          <property name="useLookupAdapter">true</property>
          <property name="interceptorName">"deviceThrowsInterceptor"</property>
        </component>
      </arg>
    </initMethod>
    <initMethod name="addAspectCustomizer">
      <arg>"aop.traceInterceptor"</arg>
    </initMethod>
    ...
  </component>

Interceptorのライフサイクルはprototypeにする。SMART Deployの規則に沿えばデフォルトでprototypeになるらしい。

追記

と思ったけど、わざわざstruts-config弄る必要ない…

public class DeviceThrowsInterceptor extends ThrowsInterceptor {

    public HttpServletRequest request;

    public HttpServletResponse response;

    /** requestでケータイかどうか判別 */
    private boolean isMobileAccess() {
        String ua = request.getHeader("User-Agent");

        // ...何か処理、ua等でケータイかどうか判別

        return true;
        
    }

    public void handleThrowable(Throwable e, MethodInvocation invocation) throws Throwable {
        if (this.isMobileAccess()) {
            if (!response.isCommitted()) {
              response.setStatus(500);
              request.getRequestDispatcher("/mobile/error.jsp").forward(request,response);
            }
        } else {
            if (!response.isCommitted()) {
                response.setStatus(500);
                request.getRequestDispatcher("/pc/error.jsp").forward(request,response);
            }
        }
    // デフォルトの500エラーページ、あるいはweb.xmlで定義した500ページに飛ばす
        throw e;
    }
}

で、いいじゃんね…