Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Java Tip 136: Protect Web application control flow

A strategy built on Struts manages duplicate form submission

  • Print
  • Feedback

Page 2 of 3

public final ActionForward perform(ActionMapping mapping,
        ActionForm form,
        HttpServletRequest request,
        HttpServletResponse response)
    throws IOException, ServletException {
    HttpSession session = request.getSession();
    ActionForward forward = null;
    if (isTokenValid(request)) {
        // Reset token and session attributes
        reset(request);
        try {
            // Perform the action and store the results
            forward = performSynchro(mapping, form, request, response);
            session.setAttribute(FORM_KEY, form);
            session.setAttribute(FORWARD_KEY, forward);
            ActionErrors errors = (ActionErrors) 
                        request.getAttribute(Action.ERROR_KEY);
            if (errors != null && !errors.empty()) {
                saveToken(request);
            }
            session.setAttribute(ERRORS_KEY, errors);
            session.setAttribute(COMPLETE_KEY, "true");
        } catch (IOException e) {
            // Store and rethrow the exception
            session.setAttribute(EXCEPTION_KEY, e);
            session.setAttribute(COMPLETE_KEY, "true");
            throw e;
        } catch (ServletException e) {
            // Store and rethrow the exception
            session.setAttribute(EXCEPTION_KEY, e);
            session.setAttribute(COMPLETE_KEY, "true");
            throw e;
        }
    } else {
        // If the action is complete
        if ("true".equals(session.getAttribute(COMPLETE_KEY))) {
            // Obtain the exception from the session
            Exception e = (Exception) session.getAttribute(EXCEPTION_KEY);
            // If it is not null, throw it
            if (e != null) {
                if (e instanceof IOException) {
                    throw (IOException) e;
                } else if (e instanceof ServletException) {
                    throw (ServletException) e;
                }
            }
            // Obtain the form from the session
            ActionForm f = (ActionForm) session.getAttribute(FORM_KEY);
            // Set it in the appropriate context
            if ("request".equals(mapping.getScope())) {
                request.setAttribute(mapping.getAttribute(), f);
            } else {
                session.setAttribute(mapping.getAttribute(), f);
            }
            // Obtain and save the errors from the session
            saveErrors(request, (ActionErrors)
                    session.getAttribute(ERRORS_KEY));
            // Obtain the forward from the session
            forward = (ActionForward) session.getAttribute(FORWARD_KEY);
        } else {
            // Perform the appropriate action in case of token error
            forward = performInvalidToken(mapping, form, request, response);
        }
    }
    return forward;
}


As you see above, the protected action is performed only once, that is, if the token is valid. If other requests are received while the action is running, they are directed to the performInvalidToken() method's result until the action completes. By default, this method simply returns an ActionForward named "synchroError". This forward should lead to a page signaling that the action is in progress and providing a button to continue. This button simply resubmits to the same action without any form or parameter in the request (they will not be considered anyway). When the action completes, it stores its forward, form, exception, and errors, if any, in the session, and it sets a flag to indicate it has completed. The first request coming after the action completion will get the forward, form, exception, and errors from the session and continue as if it was the first request itself.

I include an example in the source code to demonstrate the behavior of a simple synchronized action. The provided implementation is based on Struts 1.0.2, but can easily be adapted for release 1.1.

  • Print
  • Feedback

Resources