Thursday, April 5, 2012

Handling Duplicate Form Submissions in Struts - Using Token in Struts


Using Token in Struts



Introduction
Using the various token methods provided by the base Struts Action class allows us to easily avoid the problem of allowing double submissions of forms or simply refreshing the page after a post has been made. This source code for this application is the same source code of the Struts CRUD with just a few modifications.
Requirements
This application requires an application server that implements the Servlet 2.4 and JavaServer Pages 2.0 specifications. The examples should all work on Tomcat 5.x (Discussed in next section). Please do not e-mail about getting your application to run on a server other than Tomcat. The source code (and an Ant build file) is provided for all the lessons so you should be able to build a war from the source and run it on you application server of choice.
Using the Token methods
The methods we care about are:
  • saveToken(HttpServletRequest req)
  • isTokenValid(HttpServletRequest req)
  • resetToken(HttpServletRequest req)

The basic concept I like to follow for implementing tokens:
  1. Always provide a setupForInsertOrUpdate dispatch method or indvidual Action. You could also break it up into setupForInsert and setupForUpdate if you so desire. Regardless, make sure you always go through this Action method before you go to your form.
  2. In your setup method make sure to call "saveToken(request)." This puts a unique key into Session scope and will cause this token to be placed on your resulting JSP.
  3. In your Action's update/insert dispatch method or your Action's execute method, make sure to first check if "isTokenValid(request)." This compares the token in Session with the one submitted through the Request. If they match, the token is valid and it's ok to procede with the update/insert. If they do not match, the user is likely simply resubmitting a stale page so we return from our action immediately.
  4. We need to remember before we leave our update/insert method that we call "resetToken(request)" so that we place a new token into Session scope, otherwise the old token will remain and it will match the one on the form and will allow duplicate submisssions.
Example
The following code sections from the source you can download, demonstrates the above.

public ActionForward setUpForInsertOrUpdate(ActionMapping mapping,
ActionForm form, HttpServletRequest request, HttpServletResponse response)
throws Exception {
    logger.debug("setUpForInsertOrUpdate");
    saveToken(request);
    EmployeeForm employeeForm = (EmployeeForm)form;
    if (isUpdate(request, employeeForm)) {
        Integer id = Integer.valueOf(employeeForm.getEmployeeId());
        Employee employee = empService.getEmployee(id);
        BeanUtils.copyProperties(employeeForm, employee);
    }
    prep(request);
    return mapping.findForward(Constants.SUCCESS);
}


    public ActionForward insertOrUpdate(ActionMapping mapping,
    ActionForm form, HttpServletRequest request, HttpServletResponse response)
    throws Exception {
    logger.debug("insertOrUpdate");
    EmployeeForm employeeForm = (EmployeeForm)form;
    if ( !isTokenValid(request) ) {
        return mapping.findForward(Constants.INVALID_TOKEN);
    }
    if (validationSuccessful(request, employeeForm)) {
        Employee employee = new Employee();
        BeanUtils.copyProperties(employee, employeeForm);
        if (isUpdate(request, employeeForm)) {
            logger.debug("update");
            empService.updateEmployee(employee);
        } else {
            logger.debug("insert" );
            empService.insertEmployee(employee);
        }
        populateEmployees(request);
        resetToken(request);
        return mapping.findForward(Constants.SUCCESS);
    } else {
        prep(request);
        return mapping.findForward(Constants.FAILURE);
    }
}
Usage
After installing the app, either by deploying the war or building from the source yourself, run the application and click on the "Add Employee" link. After you add the employee you'll be brought to the list of employees and you should see the employee you entered. Now use your browser's back button to go back to the form. You should still see your data entered on the form. Hit submit and you'll see how the token helps. You can also test out the use of the token by 'refreshing' the page after you do an insert and you are brought to the employees screen. You'll probably get prompted to 'repost' - click yes.
Other ideas
There are other things you can also do besides just using the token that often help for preventing resubmits of data. You can mark form pages as non-cachable with a "no-cache, no-store" cache-control header. You can also make sure to do redirects after you submit instead of forwarding to a results page. 

1 comment: