开发者

Struts 2 s:action tag doesn't render action errors if any are present

开发者 https://www.devze.com 2023-01-23 00:45 出处:网络
For example I have the following: struts.xml: <action name=\"personForm\"> <result>/jsp/personForm.jsp</result>

For example I have the following:

struts.xml:

<action name="personForm">
    <result>/jsp/personForm.jsp</result>
</action>
<action name="savePerson">
    <result>/jsp/successSave.jsp</result>
    <result name="input">/jsp/personForm.jsp</result>
</action>
<action name="countries">
    <result>/jsp/countries.jsp</result>
</action>

personForm.jsp:

<%@ taglib prefix="s" uri="/struts-tags" %>
<s:form action="savePerson">
        <s:textfield name="firstName" label="First Name" />
        <s:textfield name="lastName" label="Last Name" />
        <s:action name="countries" executeResult="true" />
        <s:submit />
</s:form>

CountriesAction.java:

public class CountriesAction extends ActionSupport {
    public String execute() {
        countries = getCountries();
        return SUCCESS;
    }

    private Map<String, String> getCountries() {
            ...
    }

    private Map<String, String> countries;  
}

countries.jsp:

    <%@ taglib prefix="s" uri="/struts-tags" %>
    <s:select name="countryId" label="Countries" list="countries" 
        headerKey="-1" headerValue="Please select the country ..."/>

SavePerson.action

public class SavePerson extends ActionSupport {

    public void validate() {
        if (firstName == "") {
            addFieldError(firstName, "First Name is required.");
        }

        if (lastName == "") {
            addFieldError(lastName, "Last Name is required.");
        }

        if 开发者_运维知识库(countryId == "-1") {
            addFieldError(countryId, "Country is required.");
        }

    }

    public String execute() {
        //get the properties and save the person...
        return SUCCESS;
    }

    private String firstName;
    private String lastName;
    private String countryId;

    //corresponding setters and getters..
}

When I submit the form and a validation error occurs for example let's say we didn't fill in any data so the input fields 'firstName' and 'lastName' will have their corresponding message next to them. But that's not the case for the country select list, even though there are action errors those won't display.

I believe this happens because the parent action which is SavePerson is the one who added the errors (addFieldErrors) but when the other action Countries (the one that populates the list) is called then those errors are not available in that context because if I call hasErrors() within that Action it will be "false" so when the input gets rendered and check if there are any errors in order to render the message will call hasErrors and will return false so no errors messages will be rendered.

This approach of calling another action just to render another input controls is one of the ways that Struts 2 FAQS tell to do that: http://struts.apache.org/2.2.1/docs/how-do-we-repopulate-controls-when-validation-fails.html

So how can I make those controls on those actions render the action errors from its parent action.

Any thoughts?

Thank you in advance


I resolved this by setting the reference of the errors from the caller action to the invoked action. This was implemented as an interceptor:

public class CopyErrorsInterceptor extends AbstractInterceptor {


    public String intercept(ActionInvocation invocation) throws Exception {
        ValueStack stack =  invocation.getStack();
        CompoundRoot root = stack.getRoot();
        Action currentAction = (Action) invocation.getAction();
        if (root.size() > 1 && isValidationAware(currentAction)) {
            Action callerAction = getFirstActionBelow(root, currentAction);
            if (callerAction != null && isValidationAware(callerAction)) {
                ValidationAware currentActionVal = (ValidationAware) currentAction;
                ValidationAware callerActionVal = (ValidationAware) callerAction;

                //Copy the errors to the chained action.
                currentActionVal.setActionErrors(callerActionVal.getActionErrors());
                currentActionVal.setFieldErrors(callerActionVal.getFieldErrors());
            }
        }

        return invocation.invoke();
    }

    /**
     * Gets the first action below the passed action.
     * @param root the stack to find the action
     * @param current is the current action.
     * @return
     */
    private Action getFirstActionBelow(CompoundRoot root, Action current) {
        boolean foundCurrentAction = false;
        for(Object obj : root) {
            if (obj == current) {
                foundCurrentAction = true;
            } else {
                if (obj instanceof Action && foundCurrentAction) {
                    return (Action) obj;
                }
            }
        }
        return null;
    }

    private boolean isValidationAware(Action action) {
        return action instanceof ValidationAware;
    }

}

Had to declare my own stack which extends struts-default:

<interceptors>
            <interceptor name="copyErrors" class="com.afirme.casa.interceptor.CopyErrorsInterceptor"/>

            <interceptor-stack name="defaultPlusErrors">
                <interceptor-ref name="copyErrors"/>
                <interceptor-ref name="defaultStack">
                    <param name="workflow.excludeMethods">
                        input,back,cancel,execute
                    </param>
                </interceptor-ref>
            </interceptor-stack>
        </interceptors>

And to the actions that are going to be inovked by other actions (in this case through the action tags) have to reference this new stack of interceptors. Example:

<action name="example" class="ExampleAction">
            <interceptor-ref name="defaultPlusErrors"/>
            <result>/jsp/example.jsp</result>
        </action>

Hope this helps,

Alfredo O

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号