そろそろ開発は終了して製造に入りたい

だいぶ勉強もしたし、そろそろなんかシステムっぽいのを作りたくなってきた。


でも、その前に今日はPARTYテーブルを検索する機能を追加しよう。
今回はじめてsessionスコープでPOJOのActionFormを利用することになる。




PARTYテーブルに検索機能を追加する手順は

  • テストメソッドの追加。PartyDaoTest#testFindTx()を作成する。
  • テストメソッドのコンパイルコンパイルを通すためにPartySearchDtoの作成とPartyDao#find()を追加する。
  • テスト実行。PartyDao_find.sqlを作成してグリーンバーにする。
  • Actionの追加。PartySearchActionを作成する。
  • Logicメソッドの追加。PartyLogicにメソッドを追加する。
  • JSPの作成。search.jspを作成する。
  • 動作確認。

って感じかな。
作る前になんとなく手順がわかってくるのはなれてきた証拠だと思うけど、作っているときにいろいろと気づくことがあるはずだから、この手順どおりには作業は進まないと思う。


まずは、テストメソッドの追加。

    public void testFindTx() {
        deleteTable(PartyDto.TABLE);
        readXlsWriteDb("PartyTestData.xls");
        
        PartySearchDto search = new PartySearchDto();
        search.setAccount("DUMMY");
        search.setName("ダミ");

        List found = dao.find(search);
        assertNotNull(found);
        assertEquals(2, found.size());
    }

Eclipseの力をかりてコンパイルを通して、テスト実行。


2件かえってくるはずが0のためテスト失敗。予定通り。

次はグリーンバーにする作業。PartyDao_find.sqlを作成。

select *
  from PARTY
/*BEGIN*/
 where
  /*IF search.account != null && search.account != ''*/account  like /*search.account*/'us%'/*END*/
  /*IF search.name    != null && search.name    != ''*/and name like /*search.name*/'ユ%'/*END*/
/*END*/
 order by id

nullだけじゃなくて空文字列のときも条件からはずしたいからIFがちょっと複雑になってしまった。でも、こんなもんだろ。

テスト実行
失敗。


コンソールのログを見ると

select *
  from PARTY

 where
  account  like 'DUMMY'
  and name like 'ダミ'

 order by id

ってなってる。
ちゃんと'DUMMY%'となるようにPartySearchDto#getAccountLike()を追加して、PartyDao_find.sqlを少し修正。

select *
  from PARTY
/*BEGIN*/
 where
  /*IF search.account != null && search.account != ''*/account  like /*search.accountLike*/'us%'/*END*/
  /*IF search.name    != null && search.name    != ''*/and name like /*search.nameLike*/'ユ%'/*END*/
/*END*/
 order by id


テスト実行。グリーンバー。DAO完成。


次はPartySearchActionインターフェースの作成。

public interface PartySearchAction {

    public static final String[] ACTION = { "path=/site/party/search",
            "name=partySearchForm", "scope=session", "unknown=false",
            "validate=false" };

    public static final String[] search_FORWARD = { "path=/pages/party/search.jsp" };

    public static final String[] list_FORWARD = { "path=/pages/party/list.jsp" };

    // -----------------------------------------------------------------------

    public String searchInit();

    public static final String search_VALIDATE = "search";

    public String search();

}

実装は

public class PartySearchActionImpl implements PartySearchAction {

    private PartyLogic partyLogic;

    private PartySearchDto partySearchForm;

    private List parties;

    public static final String messages_EXPORT = "errors";

    private Map messages = new HashMap();

    public PartySearchActionImpl(PartyLogic partyLogic) {
        this.partyLogic = partyLogic;
    }

    public String searchInit() {
        // 検索するために必要となる情報(SelectBox情報等)はない。
        return "search";
    }

    public String search() {
        parties = partyLogic.getParties(partySearchForm);
        return "list";
    }

    // -----------------------------------------------------------------------

    public PartySearchDto getPartySearchForm() {
        return partySearchForm;
    }

    public void setPartySearchForm(PartySearchDto partySearchForm) {
        this.partySearchForm = partySearchForm;
    }

    public Map getMessages() {
        return messages;
    }

    public List getParties() {
        return parties;
    }

}

そして、まだ定義されていないPartyLogic#getParties()を作成。これは簡単。


search.jspの作成。これはメンドイけどするしかない。
たりないメッセージをMessageResources.propertiesに追加してJSP完成。


さっきの手順リストにあげ忘れてたけど、ここでDICONファイルとstruts-config.xmlへの記述追加。


Tomcat起動して動作確認。


・・・しまった。検索画面を表示する入り口をつくってなかった。
メニュー欄のパーティーリンクをクリックしたら、検索条件入力画面を表示するように変更しよう。


「キーがみつかりません。」っていうJSPエラーがでた。
今日は調子悪いかも。
MessageResources.propertiesに追加したら正常に表示できた。




うーん。ActionFormがsessionスコープになっていない。毎回空の状態で表示される。
これはちょっと調べてみないといけないけど、その前に画面遷移を修正。
あと、PartyAction#list()はもう不要となったから削除しよう。



おっと問題発覚。
検索条件入力画面から新規作成画面への画面遷移はどうしよう。
1画面1Formタグって作りにしているから検索条件入力画面のボタンを押したら、PartySearchActionを呼び出してしまう。
1画面1Formタグってしているのが問題かもしれないけど、でもそうしないと画面レイアウトが歪になりそうだし。
リンクにすれば簡単にできるけど、ボタンで表示したいし。かといって画面遷移のためのみにFormタグのAction属性を変更するJavaScriptは使いたくないし。


PartySearchActionに

    public static final String[] insertInit_FORWARD = {
            "path=/do/site/party?/site/party:insertInit=", "redirect=true" };

    public String insertInit();

を追加して、実装を

    public String insertInit() {
        return "insertInit";
    }

ってすればできるなー。


それよりもActionに指定されたメソッドが実装されてなかったら、そのメソッド名でforwardするってのはありかも知れない。


そうするか。
新しいActionExecutorとしてNameForwardActionExecutorを追加することに決定。


まずは、ActionExecutorTest#testCalledNameForwardAction()を追加。

    public void testCalledNameForwardAction() throws Exception {
        DispatchActionImpl action = new DispatchActionImpl();
        Object form = null;
        ActionMapping mapping = new MockActionMapping();
        mapping.setType(DispatchAction.class.getName());
        mapping.setPath("/do/path");

        getRequest().setParameter("/do/path:notDefineMethod", "");
        getRequest().setAttribute(Globals.MAPPING_KEY, mapping);

        ActionForward actionForward = actionExecuteProcessor
                .processActionExecute(getRequest(), getResponse(), action,
                        form, mapping);
        assertEquals("notDefineMethod", actionForward.getName());
    }

テスト実行。

NullPointerExceptionでエラー。
あまり気にせず、グリーンバーにする作業に入るかな・・・


NameForwardActionExecutorはこんな感じ

public class NameForwardActionExecutor implements ActionExecutor {

    public String execute(HttpServletRequest request, Class actionInterface,
            Object action, ActionMapping mapping) {

        String methodName = NameDispatchUtil
                .getPathMethodName(request, mapping);
        if (methodName == null) {
            return ActionExecutor.NOT_CALLED_ACTION;
        }

        NameDispatchUtil.addCalledPathMethodName(request, mapping, methodName);
        return methodName;
    }

}

s2struts.diconを修正してテスト実行。グリーンバーになった。順調順調。


Tomcatを起動して動作確認。
うまくいってる。



検索結果がゼロ件の場合にメッセージがほしいな。
若干修正して動作確認、そしてOK。





次はいよいよPOJOのActionFormがなぜsessionスコープに格納されないかの調査。
まずは、RequestProcessorを見てみる。
RequestUtilsを見てみる。前にも見たなー。なぜ見たかはわすれたけど。


うーん。
RequestUtils#canReuseActionForm()があやしい。
ActionFormオブジェクトとFormBeanConfig#getType()で取得できるクラスがいっしょかを比較してる。
POJOのActionFormを利用している場合、ActionFormはBeanValidatorFormになってFormBeanConfig#getType()はPOJOになるから常にcanReuseがfalseになる気がする。
っていうか、デバックログをコンソールに出力するようにすればすぐ確認できるようになってる。


log4j.propertiesを修正してデバックログを確認してみる。


やっぱりそうだ。
原因はわかったけど・・・
どうやればうまくいくのかわからない・・・ちょっと考える。



わからん。もうちょっと考える。




いっそS2Containerで管理させればいいのかな・・・


でも、そうするとActionConfigで指定しているScopeの意味がなくなってしまう。
ちょっとS2Strutsのドキュメントを見て勉強しよう。ヒントというか答えがあるかも。





関係ないけど、ProxyActionってかっこいいなー。
DICONファイルのname属性とaction-mappingのpath属性で関連付けを行うんだー。





やめた。これ以上考えるのやめ。わけわからんくなってきた。


独自のModuleConfigを作って、struts-config.xmlに定義されていないActionConfigやFormBeanConfigは動的に生成することにしてしまおう。
そしてPOJOのActionFormはS2Containerで管理してしまおう。ActionConfigのscopeが意味なくなるってのは無視。気にするのはやめよう。

独自のModuleConfigのイメージとしてはこんな感じ。

public class XxxModuleConfig extends ModuleConfigImpl {

    public XxxModuleConfig(String prefix) {
        super(prefix);
    }

    public ActionConfig findActionConfig(String path) {

        ActionConfig config = super.findActionConfig(path);
        if (config == null) {
            config = ActionConfigMapper.create(path);
        }

        return config;
    }

    public FormBeanConfig findFormBeanConfig(String name) {
        FormBeanConfig config = super.findFormBeanConfig(name);
        if (config == null) {
            config = FormBeanConfigMapper.create(name);
        }

        return config;

    }
}

そしてこれが完成したら、

  • AnnotationActionConfigMapper
  • RegistAnnotationActionConfigPlugIn

には引退してもらう。




そうしよう。なんか不安だけど、これも勉強とおもってがんばろう。


簡単なところから手をつけるためにXxxModuleConfigFactoryとXxxModuleConfigを作ろう。
テストクラスはなし。


XxxModuleConfigは、さっきの感じで十分。
XxxModuleConfigFactoryは、

public class XxxModuleConfigFactory extends ModuleConfigFactory {

    public ModuleConfig createModuleConfig(String prefix) {
        return new XxxModuleConfig(prefix);
    }

}

次はActionConfigUtil#create()の作成。
これはAnnotationActionConfigMapperTestを流用して作ろう。

public class ActionConfigMapperTest extends S2TestCase {

    protected void setUp() throws Exception {
        super.setUp();
        include("s2struts.dicon");
        include("ActionConfigMapperTest.dicon");
    }

    protected void tearDown() throws Exception {
        super.tearDown();
    }

    public void testMappingActionConfig() {
        ActionConfig mapping = ActionConfigMapper.create("/test");

        assertEquals("/test", mapping.getPath());
        assertEquals("study.xxx.extention.struts.config.AnnotationAction",
                mapping.getType());
        assertEquals("testForm", mapping.getName());
        assertEquals("request", mapping.getScope());
        assertEquals(false, mapping.getUnknown());
        assertEquals(false, mapping.getValidate());
    }
}

AnnotationActionはこんな感じ

public interface AnnotationAction {

    public static final String[] ACTION = { "name=testForm", "scope=request",
            "unknown=false", "validate=false" };

    public static final String[] success_FORWARD = { "path=/pages/success.jsp" };

    public static final String[] error_FORWARD = { "path=/pages/error.jsp",
            "redirect=true" };

}

そしてActionConfigMapperTest.diconは

<components>
    <component name="/test" class="study.xxx.extention.struts.config.AnnotationAction" instance="request"/>
</components>


テスト実行。NullPointerException
ActionConfigMapper#create()で空のActionMappingを返却するように修正してテスト実行。
失敗に変わった。



次はグリーンバーにする作業。


ActionConfigMapperはAnnotationActionConfigMapperを流用していっきに作成

public class ActionConfigMapper {

    private static final String ACTION = "ACTION";

    private static final String FORWARD_SUFFIX = "_FORWARD";

    public static ActionConfig create(String path) {
        S2Container container = SingletonS2ContainerFactory.getContainer();

        Class clazz = container.getComponentDef(path).getComponentClass();
        ActionConfig result = createActionMapping(clazz);
        result.setPath(path);
        result.freeze();
        return result;
    }

    private static ActionMapping createActionMapping(Class clazz) {
        BeanDesc beanDesc = new BeanDescImpl(clazz);

        if (!beanDesc.hasField(ACTION)) {
            return null;
        }
        Field field = beanDesc.getField(ACTION);
        Map map = AnnotationConfigUtil.getParameterMap(field);

        ActionMapping result = new ActionMapping();
        result.setType(clazz.getName());

        BeanUtil.populate(result, map);
        return result;
    }

}

テスト実行。グリーンバー。


ちょっと思ったんだけど、Seasar2の勉強というよりかStrutsの勉強になりつつあるなー。
勉強にはかわりないから気にするのはやめよう。




次はFormBeanConfigMapperの作成。
まずはテストクラスから。

public class FormBeanConfigMapperTest extends S2TestCase {

    protected void setUp() throws Exception {
        super.setUp();
        include("s2struts.dicon");
        include("FormBeanConfigMapperTest.dicon");
    }

    protected void tearDown() throws Exception {
        super.tearDown();
    }
    
    public void testFormBeanConfig() {
        FormBeanConfig config = FormBeanConfigMapper.create("testForm");
        assertEquals("testForm", config.getName());
        assertEquals(TestForm.class.getName(), config.getType());
        assertEquals(false, config.getDynamic());
    }

}

FormBeanConfigMapperTest.diconは

<components>
    <component name="testForm" class="study.xxx.extention.struts.config.TestForm" instance="prototype"/>
</components>

テスト実行。NullPointerException
失敗にするためにFormBeanConfigMapper#create()で空のFormBeanConfigを返却するように修正。


次はグリーンバーにする作業。
FormBeanConfigMapperを作って

public class FormBeanConfigMapper {

    public static FormBeanConfig create(String name) {
        S2Container container = SingletonS2ContainerFactory.getContainer();
        Class clazz = container.getComponentDef(name).getComponentClass();
        FormBeanConfig result = new FormBeanConfig();
        
        result.setName(name);
        result.setType(clazz.getName());
        result.freeze();
        return result;
    }
    
}

テスト実行。グリーンバーになった。





なんかそれてる。


もともとの作業目的は、POJOのActionFormのインスタンスの管理をS2Containerにさせようって思ってたんだった。
違う。
作業目的は、POJOのActionFormをsessionスコープで維持できるようにしようだった・・・




ここまできてなんだけど、わざわざS2Containerで管理しなくてもFormBeanConfig#getType()がBeanValidatorFormってかえればいいだけのような気がしてきた。
でもせっかくここまで作ったから勢いにのって作るかなー。
独自のXxxFormBeanConfigを作ってgetType()はBeanValidatorForm.class.getName()を返却して、createActionForm()はS2Containerからインスタンスを取得するって感じにしよう。


XxxFormBeanConfigは適当につくってしまおう。

public class XxxFormBeanConfig extends FormBeanConfig {

    public ActionForm createActionForm(ActionServlet servlet)
            throws IllegalAccessException, InstantiationException {

        S2Container container = SingletonS2ContainerFactory.getContainer();
        Object obj = container.getComponent(name);

        ActionForm form = new BeanValidatorForm(obj);
        form.setServlet(servlet);
        return form;

    }

}

こんな感じで。テストクラスは作ってないけど気にしないようにしよう。


DICONファイルの変更をして、web.xmlも久しぶりに変更

  <servlet>
    <servlet-name>action</servlet-name>
    <servlet-class>org.seasar.struts.servlet.S2ActionServlet</servlet-class>
    <init-param>
      <param-name>config</param-name>
      <param-value>/WEB-INF/struts-config.xml</param-value>
    </init-param>
    <init-param>
      <param-name>configFactory</param-name>
      <param-value>study.xxx.extention.struts.config.XxxModuleConfigFactory</param-value>
    </init-param>
    <load-on-startup>2</load-on-startup>
  </servlet>

こんな感じに。


struts-config.xmlにform-beanの設定と昨日追加したplug-inの設定を削除して、Tomcatを起動、動作確認。


エラー発生。

java.lang.NullPointerException
	org.apache.struts.config.ActionConfig.findException(ActionConfig.java:623)


うーん。やっぱり適当にしすぎたかなー。
ActionConfig#setModuleConfig()をしてないのが原因か。修正して再度実行。


やっぱりダメだ。空のページが表示される。
なぜ??



よく考えたら、なんでfindException()なんて呼び出してるんだろう。

ForwardConfigが取得できていないのかなー。
テストしたときはとれてたのに。




テストし忘れてた・・・・


テストを追加して、テスト実行。
失敗してる。
ActionConfigMapper#config()を修正してテスト実行。グリーンバー。

ふぅー。
今日は変なミスが多いなー。思い込みもいつも以上に激しいし。



Tomcatを起動して動作確認。


いちおうきちんと動いてる。





今日はダメだ。



最終的にできたparty.diconの内容はこれ

<components>
    <include path="study/xxx/dicon/alldao.dicon"/>
    <include path="study/xxx/dicon/allaop.dicon"/>
    
    <component class="study.xxx.logic.impl.PartyLogicImpl">
        <aspect>logicInterceptorChain</aspect>
    </component>
    
    <component name="logonForm"
              class="study.xxx.dto.PartyDto" instance="prototype"/>

    <component name="partyForm"
              class="study.xxx.dto.PartyDto" instance="prototype"/>

    <component name="partySearchForm"
              class="study.xxx.dto.PartySearchDto" instance="prototype"/>

    <component name="/auth"
              class="study.xxx.web.impl.AuthActionImpl" instance="request">
        <aspect>actionInterceptorChain</aspect>
    </component>
    
    <component name="/site/party"
              class="study.xxx.web.impl.PartyActionImpl" instance="request">
        <aspect>actionInterceptorChain</aspect>
    </component>
    
    <component name="/site/party/search"
              class="study.xxx.web.impl.PartySearchActionImpl" instance="request">
        <aspect>actionInterceptorChain</aspect>
    </component>
    
</components>

なんかstruts-config.xmlを見てるみたい。


はやめにJ2SE5.0へアップするしかないのかも・・・





今日は常にダメだったなー。
今日の勉強はなかったことにして、ソースを戻そうかなー。