Complex forms & Springs’ ServletRequestDataBinder

I caught one of my collegueas trying to hack a custom way of handling a complex HTML form. He was trying to use the parameter names of his form to create some sort of imperative language his constructs looked something like this:

updateAction/[obj_id]/[field_name]/[field_value]
createAction/[obj_type]/[field_name]/[field_value]
deleteAction/[obj_id]
...

After programming for a couple of hours he came to me to ask me how I would solve the composite fields puzzle using the above.

This is where the discussion took of.

I would NEVER actually do the above, IMHO the above conflicts with all OOP best practices and:

  • Plenty of solutions are availlable, every self respecting framework has a data binder…
  • Is hard to maintain
  • Is hard to extend
  • Even harder to test fully

During the discussing I made a classic ‘first-speak-then-tink’ mistake: I told the crowd I could build something like the above using the Springs’ databinder and a flexible object model in less then two hours.
The issue kept me thinking on my way home… was Spring up for it?

But after less then one hour of not-so-hard work I solved the above:

1. The model

I created a flexible model, which allows for extension:

Model

Java by default doesn’t automatically create non-existing referenced items in maps and lists I used a couple of nifty commons-collections utils to sort out that bit:

@SuppressWarnings("unchecked") // suppress conversion warning in the constructor, no objects in list yet
public FormGraph(){
// Lazy lists (commons-collections) automatically 'grow' when non existent objects are accessed
relations = ListUtils.lazyList(relations,FactoryUtils.instantiateFactory(FormGraph.class));
dateFields = MapUtils.lazyMap(dateFields, FactoryUtils.instantiateFactory(DateTime.class));
}

2. Configure Springs’ ServletRequestDataBinder

When I had the model finished I configured the databinder to bind request parameters to the FormGraph object:

FormGraph bean = new FormGraph();
ServletRequestDataBinder binder = new ServletRequestDataBinder(bean,"formGraphBean");
binder.bind(request);

3. Write unit test

To see if the above worked I wrote a small unit test:

[java]
// setup mock request
MockHttpServletRequest request = new MockHttpServletRequest();
// parent params
request.addParameter(“number”, “12345″);
request.addParameter(“type”, “news”);
request.addParameter(“textFields['title']“, “title”);
request.addParameter(“textFields['body']“, “body”);
// relation 0
request.addParameter(“relations['0'].number”, “456″);
request.addParameter(“relations['0'].type”, “link”);
request.addParameter(“relations['0'].textFields['url']“, “http://www.google.com”);
// relation 1
request.addParameter(“relations['1'].number”, “789″);
request.addParameter(“relations['1'].type”, “link”);
request.addParameter(“relations['1'].textFields['url']“, “http://www.yahoo.com”);
request.addParameter(“relations['1'].dateFields['created'].date”, “06-07-2006″);
request.addParameter(“relations['1'].dateFields['created'].time”, “10:11:12″);
WizardController wc = new WizardController();
try {
ModelAndView mav = wc.handleRequest(request, null);
System.out.println(mav.getModel().get(“resultBeans”));

// TODO: actual test here
} catch (Exception e) {
fail(“error while handling request: ” + e.getMessage());
}
[/java]

It works like a charm, now let’s see what he thinks about my solution!

4. Finished…

Well, not really… no error handling and validation yet… but enough to be used to demonstrate how Springs’ binding

could be used to solve the problem without writing a custom interpreter!

This entry was posted in java. Bookmark the permalink.

2 Responses to Complex forms & Springs’ ServletRequestDataBinder

  1. peter says:

    Hmm,

    after getting my colleguea to work with the code I wrote he immediately ran into a problem which is somehow caused by the binder. It seems that if a variable is present in the request multiple times the value is set using comma’s to seperate the different values… Strange!! I posted the issue on the Spring forum, but no response yet…

    After conducting some test the above seems to be a feature. If the source value is of type array (which is the case when multiple identical params are posted) and the target value isn’t the binder tries to convert the array into the target type! If the target type is an array everything is fine!