18
12月
2006

一つのformで複数の操作ボタンを設置し,一つのアクションで受ける.

一つのformで複数の操作ボタンを設置し,一つのアクションで受けるには,「どのボタンが押されたのか?」に基づいて処理する必要がある.Strutsにはこのようなパターンを想定したActionのサブクラスがある.
問題
DispatchAction
LookupDispatchAction
サンプル
注意点

問題

以下のような画面があったとする.この画面で,二つのテキストボックスに値を入力し,「足す」または「引く」ボタンを押すと,次の画面で三つ目のテキストボックスに計算結果(和または差)が出力されるようなアプリケーションを考える.以下は「足す」ボタンを押した結果である(※).以下は「引く」ボタンを押した結果である.以下は「未定義」ボタンを押した結果である.これらは本頁のサンプルの出力画面である.これらの画面は一つのフォームの中に三つのテキストフィールドと三つのボタンがある.したがって,Java Script等を使用しない場合,どのボタンを押しても一つのアクションに送信される.送信されたアクションのActionクラスでは,どのボタンが押されたかに応じて処理を行う必要がある.フォームのボタンは以下のようになっている.

<input type="submit" name="command" value="足す">
この場合,Strutsにより,ActinFormのcommandに"足す"が設定される.もっとも単純な解決方法は,この値を参照,チェックして処理を分岐させる方法である.
if (form.getCommand().equals() == "足す")
...
だがこれだとボタンに表示させるメッセージを変更する度に,JSPとActionを変更させる必要が生じる.
※ ここでボタンを押してもどうにもならない.念のため.

DispatchAction

Actionクラスがorg.apache.struts.actions.DispatchActionを拡張している場合,submitボタンのvalueの値と同名のメソッドが,executeと同じパラメータで呼び出される.例えば

というボタンが押されれば,該当Actionクラス(extends DispatchAction)でユーザが定義した(※)
public ActionForward add(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception
が呼ばれる.ただし,この仕組みを使用するにはstruts-config.xmlにて該当actionタグの属性に
parameter=”command”
などと記述しなければならない.属性parameterの値はsubmitボタンのname属性の値(すなわちJSP上ではhtml:submitカスタムタグの属性propertyの値)と揃えなければならない.
※ 呼び出すメソッドが見付からなければunspedifiedメソッドが呼ばれる.

LookupDispatchAction

DispatchActionを使用した場合でもなお問題がある.submitボタンの属性valueの値をDispatchActionのサブクラスのメソッド名と想定して,そのメソッドを呼ぼうとするので,当然ながら,メソッド名として使える文字列しか,submitボタンの属性valueの値を使用できない.すなわちボタンに「足す」とか「引く」などと記述することはできない.この問題に対応できるものとして,org.apache.struts.actions.DispatchActionがある.これはsubmitボタンのvalueの値と呼び出すメソッド名の間にメッセージリソースをはさむことにより,submitボタンの属性valueの値の制約を無くす.まず以下のようなメッセージリソースファイルがあるとする.
button.cmd.plus=足す
button.cmd.minus=引く
button.cmd.unknown=未定義
error.cmd.unknown=未定義操作です.
これに対してJSPのsubmitボタンには以下のように記述する.
  <!-- 操作1 plus -->
  <html:submit property="command">
    <bean:message key="button.cmd.plus"/>
  </html:submit>
  <!-- 操作2 minus -->
  <html:submit property="command">
    <bean:message key="button.cmd.minus"/>
  </html:submit>
  <!-- 未定義操作 -->
  <html:submit property="command">
    <bean:message key="button.cmd.unknown"/>
  </html:submit>
html:submitの属性propertyの値はsubmitボタンのinputタグの属性nameの値となる.そしてhtml:submitの内容はsubmitボタンのinputタグのvalue値となる.これにbean:messageタグを使用してメッセージリソースのキーを指定している.これでJSP出力にはキーに紐付けられた値が出力される. ActionクラスであるLookupDispatchActionのサブクラスでは
protected Map getKeyMethodMap()
をオーバーライドする.返すMapには,
このメッセージリソースキーに対してこの名前のメソッド
という対応情報を格納する.もちろんその名前のメソッドも定義する.以上をまとめるとLookupDispatchActionを使用した場合の処理はおおよそ以下になる.
(JSPでカスタムタグhtml:submitでメッセージリソースのキーを指定)
JSP出力でキーに対応するメッセージリソースのメッセージが出力される.
ユーザがsubmitボタンを押す(属性name,valueの値が送信される).
Struts該当actionの属性parameterの値と送信されたname属性の値が一致するかをチェック(※).
ActionクラスのgetKeyMethodMapでMapを取得
送信されたvalue属性の値に対応するメッセージリソース上のキーを使用して(複数ある場合は最初のものが使用される),Mapから実行すべきメソッドを特定,実行する.
実行すべきメソッドがMapから特定できなければunspecifiedが呼ばれる.
メッセージリソースのキーと値が1対1とは限らないというのは悩ましい問題かもしれない.また,Mapで特定したメソッドが見付からないという状況はバグである.洩れなくメソッドを実装すること.
※ 実際にはsubmitボタンの
属性nameの値=属性valueの値
というクエリがブラウザによって生成されているので,そのキー名のチェックになる.

サンプル

LookupDispatchActionを使用して最初の問題を解決するサンプルを示す.まずstruts-config.xml(抜粋).
<struts-config>
  <form-beans>
    <form-bean      name="MyActionForm"
                    type="matsu.struts.sample.MyActionForm"/>
  </form-beans>

  <action-mappings>
    <action    path="/SomeActions"
               type="matsu.struts.sample.SomeActions"
	       scope="request"
	       name="MyActionForm"
	       parameter="command">
      <forward name="success"        path="/some_actions.jsp" />
    </action>
  </action-mappings>
</struts-config>
要素actionに属性parameterを付けるのを忘れない.次にActionForm.
package matsu.struts.sample;

import org.apache.struts.action.*;
import org.apache.struts.actions.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.*;
import java.io.*;

public class SomeActions extends LookupDispatchAction {
    /**
     * 押されたsubmitボタンのvalueの値に
     * 該当するメッセージリソースのキーと
     * 呼び出すメソッドとのマッピング情報を返す.
     */
    protected Map getKeyMethodMap() {
	System.err.println("=== getKeyMethodMap called");
	Map map = new HashMap();
	map.put("button.cmd.plus", "plus");
	map.put("button.cmd.minus", "minus");
	return map;
    }

    /**
     * 押されたsubmitボタンのvalueの値に
     * 該当するメッセージリソースのキーが
     * button.cmd.plusの値と等しければ呼ばれる.
     */
    public ActionForward plus(ActionMapping mapping,
			      ActionForm form,
			      HttpServletRequest request,
			      HttpServletResponse response)
	throws Exception {
	System.err.println("=== plus called");
	
	MyActionForm myActionForm = (MyActionForm) form;
	try {
	    int result = (Integer.parseInt(myActionForm.getParam1())
			  + Integer.parseInt(myActionForm.getParam2()));
	    myActionForm.setResult(Integer.toString(result));
	} catch (NumberFormatException e) {
	    myActionForm.setResult("NaN");
	}
	
	return mapping.findForward("success");
    }

    /**
     * 押されたsubmitボタンのvalueの値に
     * 該当するメッセージリソースのキーが
     * button.cmd.minusの値と等しければ呼ばれる.
     */
    public ActionForward minus(ActionMapping mapping,
			       ActionForm form,
			       HttpServletRequest request,
			       HttpServletResponse response)
	throws Exception {
	System.err.println("=== minus called");

	MyActionForm myActionForm = (MyActionForm) form;
	try {
	    int result = (Integer.parseInt(myActionForm.getParam1())
			  - Integer.parseInt(myActionForm.getParam2()));
	    myActionForm.setResult(Integer.toString(result));
	} catch (NumberFormatException e) {
	    myActionForm.setResult("NaN");
	}

	return mapping.findForward("success");
    }
  
    /**
     * 押されたsubmitボタンのvalueの値に
     * 該当するメッセージリソースのキーが
     * getKeyMethodMapで返されるMapのどのキーとも
     * 一致しない場合に呼ばれる.
     */
    public ActionForward unspecified (ActionMapping mapping,
			       ActionForm form,
			       HttpServletRequest request,
			       HttpServletResponse response)
	throws Exception {
	System.err.println("=== unspecified called");

	MyActionForm myActionForm = (MyActionForm) form;
	myActionForm.setResult("NaN");

	// エラーメッセージの設定
	ActionErrors actionErrors = new ActionErrors ();
	ActionError actionError = new ActionError ("error.cmd.unknown");
	actionErrors.add(ActionErrors.GLOBAL_ERROR, actionError);
	saveErrors (request, actionErrors);
	return mapping.findForward("success");
    }
}

メッセージリソースファイルは先に示した.実行結果画面も先に示した.以下は実行時のcatalina.out.
===setCommand called : ??
=== plus called
===setCommand called : ??
=== minus called
===setCommand called : ???
=== unspecified called
なんか”?”と出ているが,これは次説のfilterによるものである(と思う).

注意点

本頁のサンプルをまとめてみた.これ.サンプルは
ant war
にてwarファイルができるので,それをデプロイする.環境によってはこれを実行する前に,
src/WEB-INF/
にStrutsのtldファイルを,
lib/
にStrutsのjarファイルを置く必要がある.実行時の注意を以下に示す(※).
native2ascii
サンプルでは
ant war
にてメッセージリソースをnative2asciiしている.こうしないとJSP出力で文字化けする.
フィルタの使用
リソースファイルはEUC_JPにて記述した.そして
ant war
にてnative2asciiされる.したがってフィルタを使用するなどしてブラウザから送信されたsubmitボタンのvalue値をEUC_JPにしなければ,実行メソッドの「Lookup」時にどれともマッチせずにunspecifiedが呼ばれてしまう.そこでサンプルのweb.xmlには以下を書いておいた.
<!DOCTYPE web-app
  PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

  <filter>
    <filter-name>Encode to EUC_JP</filter-name>
    <filter-class>filters.SetCharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>EUC_JP</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>Encode to EUC_JP</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
...省略...
あとはwarファイル展開後のディレクトリに入って
mkdir -p  WEB-INF/classes/filters/
cp $CATALINA_HOME/webapps/examples/WEB-INF/classes/filters/SetCharacterEncodingFilter.class\
   WEB-INF/classes/filters/
などとする.とにかくTomcatのexamplesのWEB-INF/classes/filters/SetCharacterEncodingFilter.classをサンプルでも使用できるように準備する.
※ もっともこのサンプルやLookupDispActionに限った注意点ではない.

You may also like...