Sunday, September 23, 2012

Using Spring Form Binding When the View Resolver Doesn't Support It (Take 2)

So in an earlier post (what is now Take 1), I covered how to expose Spring form binding to pass through everything that is needed in the event that your View resolver of choice doesn't support it (more information is available in that post).  At the time I used a handler interceptor to move the logic outside of the View resolver.   This introduced the small annoyance that there was a check on each request to see whether this was the right kind of view.  Additionally as discovered later, this also presents problems elsewhere...so time to push it back into the view resolver where it belongs.

Broken Exceptions

What caught up with me using that approach was using Spring exception handling.  Adding an exception handler to a controller, for instance, can prevent you from having to worry about system errors in addition to expected error conditions.  You obviously shouldn't be doing much with Spring form binding in your exception handling, but it's nice to be able to do something like throw the user back out to the form they were using (or another form for that matter) with an appropriate message.  For instance I was working on a form that was processed locally before communicating with a remote service.  In the case of most errors I wanted to give the user a chance to try again, but I didn't want to pollute the local code with _all_ of the remote concerns.

Into the View Resolver

But where?  Like before I wanted to keep this piece decently modularized and Spring didn't provide much to help.  After fishing through the source code for a bit the best place seemed to be to intercept the call to render() on the View interface.

 public void render(Map<String, ?> model, HttpServletRequest request,
   HttpServletResponse response) throws Exception {

This can be modified using a Decorative wrapper:

public class RequestContextViewDecorator implements View {

 private final View innerView;
 private final ApplicationContext applicationContext;
 
 public RequestContextViewDecorator(View innerView, ApplicationContext applicationContext) {
  this.innerView = innerView;
  this.applicationContext = applicationContext;
 }

The first issue is getting around that pesky wildcard capture "?" in the model. This is easily done by a small method to get back a known safe type:

 private Map<String, Object> getTypedMap(Map<String, ?> model) {
  Map%lt;String, Object> typedMap = new HashMap%lt;String, Object>();
  typedMap.putAll(model);
  return typedMap;
 }

This could easily be refactored in to an abstract class and then use a Template abstract method which receives the typed map if you have other classes doing similar things or like to add extra classes to keep things focused.  This object then inherits the same behavior covered from the first post, throwing what it needs in the model and then delegating to the wrapped View:

        @Override
 public void render(Map%lt;String, ?> untypedModel, HttpServletRequest request,
   HttpServletResponse response) throws Exception {

  Map model = getTypedMap(untypedModel);
   
  if (exposeSpringMacroHelpers) {
   if (!model.containsKey(MODEL_KEY)) {
    model.put(MODEL_KEY, new RequestContext(request, response, ((WebApplicationContext) applicationContext).getServletContext(), model));
   }
  }
....
        innerView.render(model, request, response);
     }


Now just plug in to to your view resolver (in this case the Surf(again) view resolver):


public class RequestContextPageViewResolver extends PageViewResolver {
 
 @Override
 protected View loadView(String viewName, Locale locale) throws Exception {
  return new RequestContextViewDecorator(super.loadView(viewName, locale), this.getApplicationContext());
 }
}


And there you have it (after you wire in your View resolver of choice in your Spring config): a nice OOP way of integrating the request context needed for Spring form binding in a modular way to a View resolver which for some reason or another does not have the functionality in its inheritance hierarchy.