struts2 validation using message store interceptor

2019-01-20 17:17发布

consider this:

  1. user clicks on a link
  2. request goes to DisplayLoginAction
  3. Login.jsp is displayed
  4. user enters his credentials
  5. form is mapped to ValidateLoginAction
  6. validation fails in ValidateLoginAction
  7. ValidateLoginAction stores the errors and returns "input"
  8. redirecting to DisplayLoginAction..
  9. DisplayLoginAction retrieves the errors from the interceptor and shows them to the user above the login form
  10. user refreshes the page
  11. The errors are gone

How should I save the errors on page refresh?

<action name="displayLoginPage" class="DisplayLoginAction">
   <interceptor-ref name="store">
    <param name="operationMode">RETRIEVE</param>
    </interceptor-ref>        
   <interceptor-ref name="customStack"/> 
     <result name="success">Login.jsp</result>
     <result name="input">Login.jsp</result>  
</action> 

<action name="validateloginForm" class="ValidateLoginAction">
   <interceptor-ref name="store">
    <param name="operationMode">STORE</param>
   </interceptor-ref>
   <interceptor-ref name="customStack"/> 
     <result name="input" type="redirectAction">displayLoginPage</result>
     <result name="success">LoginConfirmation.jsp</result>
</action>

1条回答
够拽才男人
2楼-- · 2019-01-20 17:42
  1. user refreshes the page

  2. The errors are gone

How should I save the errors on page refresh?

That is the way MessageStoreInterceptor works. It is actually a feature, not a bug.

Page refresh is an action triggered by the user, that means it can be assumed he has already read the result of the previous operation (the login attempt), unless he is pressing F5 with the eyes closed.

You should WANT a message to expire after the first read.

Consider a page with a lot of non-ajax operations, like combobox depending on others, etc... If the error message is persistent, it would popup after each submit operation that does not involve going to another page.

You don't want that. You should want to get the message saying that one operation has gone wrong (or right) just after that operation. If the user then proceed with other operations, like a refresh, if those operations aren't going in error (or in a specific success state), no message should be shown.

After that, there's also the problem of when deleting from session a persistent message, because otherwise you would get that message in the next action with a RETRIEVE or AUTOMATIC operationMode. Then you could (but shouldn't) customize the MessageStore Interceptor, or writing / reading / deleting messages from and to the session on your own, basically reinventing the wheel. Or even put two MessageStore Interceptors, one RETRIEVE and one STORE for displayLoginPage Action, getting the pitfalls just mentioned.

You are also using the PRG pattern (Post/Redirect/Get), to avoid re-sending the data when refreshing the page. At the same way, you should avoid re-reading the same messages twice.

To see how this specifically works, you can take a look at the MessageStore Interceptor source code, that is quite simple:

  • Before Invocation, if Action is ValidationAware and operationMode is RETRIEVE or AUTOMATIC:
    1. read actionMessages, actionErrors, fieldErrors from session;
    2. merge them with current action's actionMessages, actionErrors, fieldErrors;
    3. remove actionMessages, actionErrors, fieldErrors from session.
/**
 * Handle the retrieving of field errors / action messages / field errors, which is
 * done before action invocation, and the <code>operationMode</code> is 'RETRIEVE'.
 *
 * @param invocation
 * @throws Exception
 */
protected void before(ActionInvocation invocation) throws Exception {
    String reqOperationMode = getRequestOperationMode(invocation);

    if (RETRIEVE_MODE.equalsIgnoreCase(reqOperationMode) ||
            RETRIEVE_MODE.equalsIgnoreCase(operationMode) ||
            AUTOMATIC_MODE.equalsIgnoreCase(operationMode)) {

        Object action = invocation.getAction();
        if (action instanceof ValidationAware) {
            // retrieve error / message from session
            Map session = (Map) invocation.getInvocationContext().get(ActionContext.SESSION);

            if (session == null) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Session is not open, no errors / messages could be retrieve for action ["+action+"]");
                }
                return;
            }

            ValidationAware validationAwareAction = (ValidationAware) action;

            if (LOG.isDebugEnabled()) {
                LOG.debug("retrieve error / message from session to populate into action ["+action+"]");
            }

            Collection actionErrors = (Collection) session.get(actionErrorsSessionKey);
            Collection actionMessages = (Collection) session.get(actionMessagesSessionKey);
            Map fieldErrors = (Map) session.get(fieldErrorsSessionKey);

            if (actionErrors != null && actionErrors.size() > 0) {
                Collection mergedActionErrors = mergeCollection(validationAwareAction.getActionErrors(), actionErrors);
                validationAwareAction.setActionErrors(mergedActionErrors);
            }

            if (actionMessages != null && actionMessages.size() > 0) {
                Collection mergedActionMessages = mergeCollection(validationAwareAction.getActionMessages(), actionMessages);
                validationAwareAction.setActionMessages(mergedActionMessages);
            }

            if (fieldErrors != null && fieldErrors.size() > 0) {
                Map mergedFieldErrors = mergeMap(validationAwareAction.getFieldErrors(), fieldErrors);
                validationAwareAction.setFieldErrors(mergedFieldErrors);
            }
            session.remove(actionErrorsSessionKey);
            session.remove(actionMessagesSessionKey);
            session.remove(fieldErrorsSessionKey);
        }
    }
}
  • After Invocation, if Action is ValidationAware and operationMode is STORE or (operationMode is AUTOMATIC and Result is of type redirectAction):
    1. read actionMessages, actionErrors, fieldErrors from action;
    2. store actionMessages, actionErrors, fieldErrors in the session.
/**
 * Handle the storing of field errors / action messages / field errors, which is
 * done after action invocation, and the <code>operationMode</code> is in 'STORE'.
 *
 * @param invocation
 * @param result
 * @throws Exception
 */
protected void after(ActionInvocation invocation, String result) throws Exception {

    String reqOperationMode = getRequestOperationMode(invocation);
    boolean isRedirect = invocation.getResult() instanceof ServletRedirectResult;
    if (STORE_MODE.equalsIgnoreCase(reqOperationMode) ||
            STORE_MODE.equalsIgnoreCase(operationMode) ||
            (AUTOMATIC_MODE.equalsIgnoreCase(operationMode) && isRedirect)) {

        Object action = invocation.getAction();
        if (action instanceof ValidationAware) {
            // store error / messages into session
            Map session = (Map) invocation.getInvocationContext().get(ActionContext.SESSION);

            if (session == null) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Could not store action ["+action+"] error/messages into session, because session hasn't been opened yet.");
                }
                return;
            }

            if (LOG.isDebugEnabled()) {
                LOG.debug("store action ["+action+"] error/messages into session ");
            }

            ValidationAware validationAwareAction = (ValidationAware) action;
            session.put(actionErrorsSessionKey, validationAwareAction.getActionErrors());
            session.put(actionMessagesSessionKey, validationAwareAction.getActionMessages());
            session.put(fieldErrorsSessionKey, validationAwareAction.getFieldErrors());
        }
        else if(LOG.isDebugEnabled()) {
        LOG.debug("Action ["+action+"] is not ValidationAware, no message / error that are storeable");
        }
    }
}

Note 1: The login operation is also self-explanatory: if after logging in you land on the login page again, it can only mean that the login failed... BTW the above explanation applies to every page/functionality
Note 2: There are sites producing messages that expire automatically after X seconds, not caring about the user having read them or not... and that is against the usability, IMHO.

查看更多
登录 后发表回答