DWR & Spring Form Controllers

During some recent development, it became apparent that I’d need some integration point between DWR and Spring Form Controllers. After researching a resolution to this, I came across a few articles. While these worked, I was confident there had to be a simpler way… and there was.

What if you could make your DWR requests pretend to be new form posts to your controller? That way Spring binds your form parameters to the model, and validation can still occur. So, why not? Ultimately, the form’s “handleRequest” method takes an HttpServletRequest (among other params), why not augment the HttpServletRequest that comes with DWR so it has the form parameters required by Spring?

There are a few (simple) hurdles to overcome. Primarily, HttpServletRequest’s parameter map is immutable, so you can’t simply add new parameters to an existing HttpServletRequest object and pass it on to the controller. Instead, you use a HttpServletRequestWrapper to wrap an existing HttpServletRequest to extract the existing params into a mutable map, that you can then programmatically add new parameters. (see the examples below)

Once you have your HttpServletRequestWrapper wrapped around your HttpServletRequest, it’s simply a matter of passing it to your controller and letting it bind and validate the values in the request. Eventually, Spring will return a ModelAndView. Within that ModelAndView will be any binding/validation errors that occurred. Now you have to extract those errors and pass them back to DWR to dynamically present on the page. This is all as simple as extracting the BindingResult from the ModelAndView and checking for any errors. Once you have the errors, you can place them back into a HashMap and return it to DWR.

This option is head and shoulders above anything else we’ve seen. It leaves DWR to do what DWR does, and leaves Spring to do what Spring does. We’re just providing some glue between the two to make them work. Very little code is actually involved in making this work, and, ultimately, is reusable across all your applications. It can be used to save individual form fields, or an entire form. It’s simply up to you what form fields you want to pass to the DWR Endpoint to wrap into the HttpServletRequest to expose to Spring.

The following is some code examples for the DWR Endpoint and DWR client-side javascript.

HttpRequestWithModifiableParams – HttpServletRequestWrapper
Here’s the Servlet Request Wrapper you can use to make the Request object’s parameters mutable. You can simply use this as-is:

package edu.csun.sa.elections.util;

import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class HttpRequestWithModifiableParams extends HttpServletRequestWrapper{
    Map params;

    /**
     * @param request
     */
    public HttpRequestWithModifiableParams(HttpServletRequest request) {
        super(request);
        this.params = new HashMap(request.getParameterMap());
    }

    public String getParameter(String name) {
        String returnValue = null;
        String[] paramArray = getParameterValues(name);
        if (paramArray != null && paramArray.length > 0){
          returnValue = paramArray[0];
        }
        return returnValue;
    }

    public Map getParameterMap() {
        return Collections.unmodifiableMap(params);
    }

    public Enumeration getParameterNames() {
        return Collections.enumeration(params.keySet());
    }

    public String[] getParameterValues(String name) {
    	String[] result = null;
    	String[] temp = (String[])params.get(name);
    	if (temp != null){
    		result = new String[temp.length];
    		System.arraycopy(temp, 0, result, 0, temp.length);
    	}
        return result;
    }

    /**
     * Sets the a single value for the parameter.  Overwrites any current values.
     * @param name Name of the parameter to set
     * @param value Value of the parameter.
     */
    public void setParameter(String name, String value){
      String[] oneParam = {value};
      setParameter(name, oneParam);
    }

    /**
     * Sets multiple values for a parameter.
     * Overwrites any current values.
     * @param name Name of the parameter to set
     * @param values String[] of values.
     */
    public void setParameter(String name, String[] values){
      params.put(name, values);
    }
}

DWR Endpoint
We created a simple Java bean as an endpoint for DWR to proxy requests through – although, this would probably be better served as a Spring AOP Proxy. DWR 2.0 allows you to declare that a method will receive an HttpServletRequest and an HttpServletResponse, which does not show up in the DWR signature. It automatically knows to include these implicitly:

public class DwrEndpoint {

  // This is the controller we want to pass the modified request through
  private Controller formController = null;

  // This is the DWR endpoint we'll be using to proxy the request through
  // Notice that it takes an HttpServletRequest and HttpServletResponse, which DWR will handle automatically
  // Ultimately, all we need to pass to this method in DWR is a javascript suedo-hashmap (see below)
  public Map saveFormField(HttpServletRequest req, HttpServletResponse res, Map formParams) {

    // This map will hold errors mapping to the field (key) and the error (value)
    // The intrinsic issue with this is that only one error per field can be returned
    Map errors = new HashMap();

      try {
        // See above for the custom HttpServletRequestWrapper
        HttpRequestWithModifiableParams newReq = new HttpRequestWithModifiableParams(req);

        if( formParams != null ) {
          // We will iterate through the form params to place them in the servlet params
          Iterator i = formParams.keySet().iterator();
          while( i.hasNext() ) {
            String key = (String) i.next();
            key = URLDecoder.decode(key, "UTF-8");

            String value = (String) formParams.get(key);
            String[] values = value.split(",");
            for(int x=0; x < values.length; x++)
              values[x] = URLDecoder.decode(values[x], "UTF-8");

            // Parameters separated by "," need to be stored as an array
            if(values.length > 1)
              newReq.setParameter(key, values);
            else
              newReq.setParameter(key, value);
          }
        }

        // Send our modified request to the form - at this point Spring takes over
        // and we'll receive a ModelAndView in response.
        ModelAndView mav = formController.handleRequest(newReq, res);
        ModelMap map = mav.getModelMap();

        // Extract any errors and place them in a map back to the JS client.
        // As a note: this needs work, since only one error per field can be returned as-is.
        BindingResult br = (BindingResult) map.get("org.springframework.validation.BindingResult." + formController.getCommandName());
        if( br != null ) {
          if( br.getErrorCount() > 0 ) {
            for( Object obj : br.getFieldErrors() ) {
              FieldError error = (FieldError) obj;
              errors.put(error.getField(), error.getDefaultMessage());
            }
          }
        }

      } catch( Exception e ) {
        log.error("Error in DWR call", e);
      }
    }

    return errors;
  }

Lastly, the JS code to call DWR with. Remember, our JS needs to pass the NAME and VALUE of the form to DWR, just as a POST would pass them to the Form Controller. These pairs will eventually be converted into Request Parameters, and Spring will bind them to the command object appropriately.

    <script type='text/javascript'>
      function saveFormField(id) {
        var form = document.myForm;

        // obj will simulate a HashMap to be passed via DWR to our endpoint
        var obj = new Object();
        // get the form object we want to save
        elem = form.elements[id];

        // add the name and value to the "hashmap"
        obj[elemName] = elem.value;

        // Create a hashmap to store an the ID to be passed to the callback
        // (this format is for DWR 2.x, there's an easier format for 3.x)
        var params = new Object();
        params['id'] = id;

        // Define a callback function from the saveFormField call, this will allow us to
        // dynamically update the page with Spring Validation errors.
        var callbackProxy = function(response){
          saveFormFieldCallback(response, params);
        };

        // Define the callback variable
        var callMetaData = { callback:callbackProxy };

        // Call DWR, passing the object containing our "hashmap" of form name & value,
        // and the callback variable to execute after DWR returns.
        DwrEndpoint.saveFormField( obj, callMetaData );
      }

      // The callback will output the validation error from Spring, or clear the field if no error occurred.
      function saveVoteCallback(obj, params){
        if(typeof obj != 'undefined'){
          var key = "";
          for(key in obj){}
          if(key == '')
            dwr.util.setValue("errors_" + params["id"], "");
          else
            dwr.util.setValue("errors_" + key, obj[key]);
        }
      }
    </script>

In the end, you have a Javascript call to DWR passing name/value pairs representing form fields, to which DWR passes to your DWR-exposed Java bean that translates the name/value pairs into HttpServletRequest parameters and passes it on to Spring to bind and validate. On the return, Spring passes back a ModelAndView containing any binding/validation errors that are returned to your Javascript to present to the user. DWR doesn’t know about Spring, and Spring doesn’t know about DWR, just as it should be.

3 Responses to “DWR & Spring Form Controllers”

  1. ananomouse1 says:

    Two questions:

    1) In your DwrEndpoint class, how is the formController variable set?

    2) Can you post an actual working sample? What does your JSP and Spring config file look like?

    Thanks

  2. ananomouse1 says:

    As a follow up: What does you dwr.xml file look like? (or its equivalent)? Lots of info missing for us newbies…

  3. Halcyon says:

    I don’t have an example on hand, but I can dig one up for you if you give me a day or so. In the DwrEndpoint class the formController will be injected via Spring dependency injection. The dwr.xml file should simply define your DwrEndpoint and exposed method, nothing special. The JSP and Spring files will be no different than usual. The idea is that the JSP shouldn’t care whether the form is being submit via DWR or HTTP POST.

Leave a Reply

You must be logged in to post a comment.