LookupDispatchAction + エンター


izuさんのところのむかーしの記事のタイトルをぱくってみました。


LookupDispatchActionはクリックされたボタンにより処理を振り分けるもの。

例えばこんな風にしておけば、

<html:form action="/hoge">
	<html:submit property="action"><bean:message key="button.moge"/></html:submit>
	<html:submit property="action"><bean:message key="button.hage"/></html:submit>
</html:form>

クリックされたボタンにより処理の振分が行われる。


ところが、次のようにフォーム内に入力フィールドなどのオブジェクトが一つだけある場合に困ったことになる。
例えばこんなソース。

<html:form action="/hoge">
	<html:input property="hogehoge"/>
	<html:submit property="action"><bean:message key="button.moge"/></html:submit>
	<html:submit property="action"><bean:message key="button.hage"/></html:submit>
</html:form>


このJSPIEからPOSTするのだが、入力フィールドにカーソルを当てて、enterキーを叩くと画面が白くなる。
理由はStrutsのソースをデバッグしてみると分かるのだが、LookupDispatchActionのgetMethodName()内で「どのボタンがクリックされたのか」のパラメータ(keyName)が取得できていないため、nullが返却されるのだ。

protected String getMethodName(
    ActionMapping mapping,
    ActionForm form,
    HttpServletRequest request,
    HttpServletResponse response,
    String parameter)
    throws Exception {

    // Identify the method name to be dispatched to.
    // dispatchMethod() will call unspecified() if name is null
    String keyName = request.getParameter(parameter);
    if (keyName == null || keyName.length() == 0) {
        return null;
    }

    String methodName = getLookupMapName(request, keyName, mapping);

    return methodName;
}


当然Forward先"null"は存在しないので画面が白くなるというわけだ。
※複数のフィールドがある場合や、FireFoxではこの現象はでていない。その画面の最も最初に出てくるボタンのパラメータが送られてくる。
※requestをデバッグしてみると、「request-coyoteRequest-parameters-paramHashStringArray-table」の中に正常な場合はディスパッチのキーが含まれているのが分かる。


さてどうするか。
izuさんはデフォルトのForwardを定義しているが、今回は大先輩に逆らってみよう。


ディスパッチを使用する場合、僕はvalidateを飛ばした先で行っている。
そこで、validateで使用するinputを借用して戻る画面を定義してみることに。


struts-confix.xml

<action path="/hoge"
        parameter="action"
        type="hoge.MyDispatchAction"
        scope="request"
        input="/hogeInput"
        validate="false">
    <forward name="moge" path="/moge.do"/>
    <forward name="hage" path="/hage.do"/>
</action>


izuさんの案に手を入れたもの
hoge.MyDispatchActionはLookupDispatchActionを継承したこのクラスをさらに継承する。

public abstract class LookupDispatchCustomAction extends LookupDispatchAction {

  public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response ) throws Exception {

    if ( request.getParameter( mapping.getParameter() ) == null ) {
      String input = mapping.getInput();
      if(input != null){
        //inputが定義されている
        ActionForward af = new ActionForward();
        af.setPath(input);
        af.setRedirect(false);
        return af;
      }else{
        // 不明
        throw new UnknownDispatchException();
      }
    } else {
      //LookupDispatchActionに処理をまかせる
      return super.execute( mapping, form, request, response );
    }
  }


abstractで定義してあるのは、スーパークラスのLookupDispatchActionがgetKeyMethodMap()を必要とするため。


こうしておけば、パラメータが送られてこないときでもinputが定義されていればその画面に飛ぶし、なければきちんとエラーとして扱うことができる。


もう一つ考えたのは、Actionクラス全般を処理後にインターセプトして、ActionForward先がnullの場合はエラーにするもの。
AOPを使えばできるだろうけど、後から分かり難くなりそうなので今回はちょっとパス。



参考
izuさんの日記*1
@ITに同じような質問があったけれども、解決策は提示されず*2


あとは海外の掲示板で2〜3件あったぐらいかな?
今回Strutsのソースを直接調べたのが勉強になったなぁ・・・



やっぱり気になったので後日修正
"default"というforwordを定義しておいて、存在しないならエラーにすることに

  private static final String DEFAULT_FORWORD_NAME = "default";

  public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response ) throws Exception {

    if ( request.getParameter( mapping.getParameter() ) == null ) {

      ActionForward def = mapping.findForward(DEFAULT_FORWORD_NAME);
      if(def != null){
        //defaultが定義されている場合はそこに戻るようにする
        return def;
      }else{
        // それ以外はエラー画面に遷移する
        throw new UnknownDispatchException();
      }
    } else {
      //LookupDispatchActionに処理をまかせる
      return super.execute( mapping, form, request, response );
    }
  }