どこまでできるかな

今日は、Actionメソッド単位のvalidateを実装する。


久しぶりにStrutsのサイトにいったら、バージョンがアップしてた。
1.2.4から1.2.7へ。


Release Notesを見て確認。
commons-collections.jarはいらなくなったみたい。

うーん。英語は読めないからよくわからん。気にするのはやめよう。
以下のjarとtldxmlを更新。

struts.jar
struts-el.jar
struts-bean-el.tld
struts-html-el.tld
struts-logic-el.tld
validator-rules.xml

動くか確認。
LOGON/LOGOFFはうまく動いた。Strutsバージョンアップ完了。



次はvalidateのInterceptorを作る。


Actionインターフェースで

    public static final String execute_VALIDATE = "/pages/error.jsp"
    public String execute();

って感じで記述があったら、validateを行うように実装しよう。


まずはテストクラスを作らないと

public class ValidateInterceptorTest extends S2TestCase {

    private ActionExecuteProcessor actionExecuteProcessor;

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

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

    public void testValidator() throws Exception {
        ValidateAction action = (ValidateAction) getComponent(ValidateAction.class);
        ValidateMessageForm form = new ValidateMessageForm();
        ActionMapping mapping = new MockActionMapping();
        mapping.setType(ValidateAction.class.getName());
        mapping.setName("validateMessageForm");

        ActionForward actionForward = actionExecuteProcessor
                .processActionExecute(getRequest(), getResponse(), action,
                        form, mapping);

        assertEquals("called", form.getTestMessage());
    }

}

あと利用する、ActionとActionFormを作成して、

public interface ValidateAction {
    
    public static final String test_VALIDATE = "/pages/error.jsp";

    public String test();

}
public class ValidateMessageForm extends ValidatorForm {
    
    private String testMessage = "";
    
    public ActionErrors validate(ActionMapping mapping,
            HttpServletRequest request) {
        
        testMessage = "called";
        return null;
    }
    
    public String getTestMessage() {
        return testMessage;
    }

}

DICONファイルを記述してテスト実行。
期待した失敗。
次はValidateInterceptorを作ってテストを通す作業。


ValidateInterceptorを作るためにTraceInterceptorを参考にしてみる。


うーん。もうちょっと参考がほしいなー。
S2StrutsのActionExecuteProcessorImplとBindingUtilも参考にすればできそうだ。

public class ValidateInterceptor extends AbstractInterceptor {

    private static final String REQUEST = "request";

    private static final String VALIDATE_SUFFIX = "_VALIDATE";

    public Object invoke(MethodInvocation invocation) throws Throwable {
        String validateName = invocation.getMethod().getName() + VALIDATE_SUFFIX;

        ComponentDef componentDef = getComponentDef(invocation);
        BeanDesc beanDesc = new BeanDescImpl(componentDef.getComponentClass());

        if (beanDesc.hasField(validateName)) {
            if (!validate()) {
                Field validateField = beanDesc.getField(validateName);
                return (String) FieldUtil.get(validateField, null);
            }
        }

        return invocation.proceed();
    }
    
    public boolean validate() {
        S2Container container = SingletonS2ContainerFactory.getContainer();
        HttpServletRequest request = container.getRequest();
        ActionMapping mapping = (ActionMapping) request
                .getAttribute(Globals.MAPPING_KEY);

        Object form = getActionForm(request, mapping);
        if (form == null || !(form instanceof ActionForm)) {
            return true;
        }

        ActionErrors errors = ((ActionForm) form).validate(mapping, request);
        if (errors == null || errors.isEmpty()) {
            return true;
        }
        request.setAttribute(Globals.ERROR_KEY, errors);

        return false;
    }

    public Object getActionForm(HttpServletRequest request,
            ActionMapping mapping) {

        if (REQUEST.equals(mapping.getScope())) {
            return request.getAttribute(mapping.getAttribute());
        } else {
            HttpSession session = request.getSession();
            return session.getAttribute(mapping.getAttribute());
        }
    }

}

こんな感じかな。DICONファイルを修正してテスト実行。


NullPointerException


うーん。障害トレースを見るとActionMappingがnullみたい。
あらかじめ、requestにActionMappingとActioFormをsetAttribute()しないといけないのかも。
あと、ActionMapping#setScope()もしてなかった。これも追加しよう。

    public void testActionMessage() throws Exception {
        ValidateAction action = (ValidateAction) getComponent(ValidateAction.class);
        ValidateMessageForm form = new ValidateMessageForm();
        ActionMapping mapping = new MockActionMapping();
        mapping.setType(ValidateAction.class.getName());
        mapping.setName("validateMessageForm");
        mapping.setScope("request");
        
        getRequest().setAttribute(Globals.MAPPING_KEY, mapping);
        getRequest().setAttribute("validateMessageForm", form);

        ActionForward actionForward = actionExecuteProcessor
                .processActionExecute(getRequest(), getResponse(), action,
                        form, mapping);

        assertEquals("called", form.getTestMessage());
    }

テスト実行。グリーンバー。
無理やり通したって感じだ。


作ってて思ったことが2点。
ValidateInterceptor#validate()でfalseのときに戻す値はforward名だから、"/pages/error.jsp"ではなくて"error"みたいにしないといけない。
ValidateInterceptor#validate()は、後で修正が必要になる予感。
ActionFormUtilに抜き出しておこう。そうしたらValidateInterceptorが単純になるし。

抜き出した後テスト実行。グリーンバー。問題なし。


次はいよいよAuthActionへの適用。
まずは、allaop.diconのactionInterceptorChainにさっき作ったValidateInterceptorを追加。
AuthActionインターフェースの修正。

そしてTomcatを起動して手動による確認。

validateしていない。ダメだ。
なぜ?
よくわからん。



こういうときは、System.out.printlnデバッグだ。


POJOのActionFormだからみたい。
それでvalidateできていない。
もうちょっと調査しよう。


XxxActionExecuteProcessorImpl#toBeanFromActionForm()で、requestにあるBeanValidatorFormをPOJOFormに変えてる。
それでか。


どうしよう




XxxActionExecuteProcessorImpl#toBeanFromActionForm()をしなくてもうまくPOJOActionにセットされるように変更。

どんなテストをつくればいいんだろう。

    public void testBindingActionForm() throws Exception {
        BindingActionFormActionImpl action = new BindingActionFormActionImpl();
        Object form = new BeanValidatorForm(new BindingActionForm("test"));
        ActionMapping mapping = new MockActionMapping();
        mapping.setType(BindingActionFormAction.class.getName());
        mapping.setName("bindingActionForm");
        mapping.setScope("request");
        
        getRequest().setAttribute("bindingActionForm", form);

        ActionForward actionForward = actionExecuteProcessor
                .processActionExecute(getRequest(), getResponse(), action,
                        form, mapping);

        assertEquals("Binding=OK Request=OK ", action.getTestMessage());
    }
public class BindingActionFormActionImpl implements BindingActionFormAction {

    private BindingActionForm bindingActionForm = null;

    private String testMessage = "";

    public String test() {
        HttpServletRequest request = SingletonS2ContainerFactory.getContainer()
                .getRequest();

        if (bindingActionForm != null
                && bindingActionForm.getMessage().equals("test")) {
            testMessage += "Binding=OK ";
        }

        if (request.getAttribute("bindingActionForm") instanceof BeanValidatorForm) {
            testMessage += "Request=OK ";
        }

        return "success";
    }

    public BindingActionForm getBindingActionForm() {
        return bindingActionForm;
    }

    public void setBindingActionForm(BindingActionForm bindingActionForm) {
        this.bindingActionForm = bindingActionForm;
    }

    public String getTestMessage() {
        return testMessage;
    }

}

なんか変なテストだな・・・。こんなもんか?
テスト実行。期待通りの失敗。


次はグリーンバーにするためにXxxActionExecuteProcessorImplとXxxActionPropertyBinderImplを変更。

XxxActionPropertyBinderImpl#importProperties()は以下のように変更。

    public void importProperties(Object action, S2Container container,
            BeanDesc beanDesc) {
        importParameter(action, container);
        for (int i = 0; i < beanDesc.getPropertyDescSize(); i++) {
            PropertyDesc propertyDesc = beanDesc.getPropertyDesc(i);

            if (isActionFormProperty(container, propertyDesc)) {
                importActionFormProperty(action, container, propertyDesc);
            } else {
                importProperty(action, container, propertyDesc);
            }
        }
    }

    private void importActionFormProperty(Object action, S2Container container, PropertyDesc propertyDesc) {

        HttpServletRequest request = container.getRequest();
        ActionMapping mapping = (ActionMapping) request
                .getAttribute(Globals.MAPPING_KEY);

        Object form = ActionFormUtil.getActualActionForm(request, mapping);
        propertyDesc.setValue(action, form);
    }

    private boolean isActionFormProperty(S2Container container,
            PropertyDesc propertyDesc) {

        ActionMapping mapping = (ActionMapping) container.getRequest()
                .getAttribute(Globals.MAPPING_KEY);

        return propertyDesc.getPropertyName().equals(mapping.getAttribute());
    }

あとXxxActionExecuteProcessorImpl#toBeanFromActionForm()とsetActionForm()を削除。


そしてテスト実行。


NullPointerException


・・・まただ。requestにActionMappingをsetAttribute()してないのが原因。
今まで作ったテストメソッドにも追加してやらないといけない・・・


処理を追加して再度テスト実行。
グリーンバー。できたかも。


さっそくTomcatを起動して手動による確認。

できた。やった。


XxxActionPropertyBinderImplが汚くなってきたからきれいにしよう。
・・・あんまりきれいにならない。


テストクラスのメソッドも移動したり、クラス名を変えたりしよう。
XxxActionExecuteProcessorImplTestって結局、XxxActionExecuteProcessorImplのテストっていうよりは、
適切なActionExecutorが呼び出せているかのテストときちんとActionにPropertyをImport/Exportできているかのテストになっている。
ActionExecutorTestとActionPropertyBindingTestに分けよう。
それにあわせてテストメソッド名や利用しているActionの名前も変えよう。