I have the following (simplified) form in one of my view:
<form:form commandName="entry" method="POST">
<form:input type="text" path="name"/>
<form:input type="text" path="tags" />
<input type="submit" value="Submit"/>
</form:form>
Which is going to be bind to the following JavaBean:
public class Entry {
private String name;
private List<Tag> tags = new LinkedList<Tag>();
// setters and getters omitted
}
because I want to take use all new fancy features of Spring 3, I'm using annotation-driven controller to receive the POST request:
@Controller
@RequestMapping("/entry")
public class EntryController {
@RequestMapping(method = RequestMethod.GET)
public ModelAndView show() {
ModelAndView mav = new ModelAndView("entry");
mav.addObject(new Entry());
return mav;
}
@RequestMapping(method = RequestMethod.POST)
public String add(@ModelAttribute("entry") @Valid Entry entry,
BindingResult result) {
// check validation from Binding result
// execute method on business beans: adding this entry to the system
// return a view if correct
}
}
As you can see, I need to convert my input text (which look like tag1, tag2, tag3
) as a list of Tag, define like this:
public class Tag {
private String name;
// setter and getter omitted
}
There is several strategies to do this with Spring 3.0:
(Sorry long post, questions are in bold)
Programming a new property tagsAsText
to have a getter/setter as String:
public class Entry {
// ...
public void setTagsAsText(String tags) {
// convert the text as a list of tags
}
public String getTagsAsText() {
// convert list of tags to a text
}
}
This approach has two drawbacks:
BindingResult
in the case of error in the string ?I can also use a BeanInfo for my bean:
public class EntryBeanInfo extends SimpleBeanInfo {
public PropertyDescriptor[] getPropertyDescriptors() {
try {
@Override
PropertyDescriptor tagsDescriptor = new PropertyDescriptor("tags", Entry.class) {
@Override
public PropertyEditor createPropertyEditor(Object bean) {
return new EntryTagListEditor(Integer.class, true);
};
};
// omitting others PropertyDescriptor for this object (for instance name)
return new PropertyDescriptor[] { tagListDescriptor };
}
catch (IntrospectionException ex) {
throw new Error(ex.toString());
}
}
}
And declare one converter
public class EntryTagListEditor extends PropertyEditorSupport {
public void setAsText(String text) {
// convert the text to a list of Tag
}
public String getAsText() {
// convert the list of Tag to a String
}
}
This approach has also two drawbacks:
BindingResult
in the case of error in the string ?Converter uses the generic mechanism of Java 5:
final class StringToTagList implements Converter<String, List<Tag>> {
public List<Tag> convert(String source) {
// convert my source to a list of Tag
}
}
This approach looks more elegant but still two drawbacks:
ConversionServiceFactoryBean
, is there any way to keep the default Converters ?BindingResult
in the case of error in the string ?Spring 3.0 introduces a simple Converter interface that you can implement and reuse anywhere in Spring. You can use them in Spring MVC to convert request String values to Controller method parameter values of any Object type that you can write a Converter for.
A converter converts a source object of type S to a target of type T . Implementations of this interface are thread-safe and can be shared.
Spring provides out-of-the-box various converters for built-in types; this means converting to/from basic types like String, Integer, Boolean and a number of other types.
HttpMessageConverter is a strategy interface that specifies a converter that can convert from and to HTTP requests and responses in Spring REST Restful web services. Internally Spring MVC uses it to convert the Http request to an object representation and back.
A well-considered question, even it'll scare most people off :)
Anyway, I think option (2) is the closest to a practical solution. My first suggestion is that you encapsulate the list of tags into its own model class. This will give the data binding framework a concrete type to register against, whereas List
and String
are much too general.
So you would have the model classes:
public class Entry {
private String name;
private TagList tagList;
}
public class TagList {
private final List<Tag> tags;
public TagList(List<Tag> tags) {
this.tags = tags;
}
public List<Tag> getTags() {
return tags;
}
}
You then have a PropertyEditor
that knows how to convert to and from a TagList
:
public class TagListEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
TagList tagList = // parse from the text value
setValue(tagList);
}
@Override
public String getAsText() {
TagList tagList = (TagList) getValue();
return tagList.toString(); // or whatever
}
}
And finally, you need to tell the controller to use the converter:
@Controller
public class EntryController {
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(TagList.class, new TagListEditor());
}
// request mappings here
}
I'm fairly sure the new Spring 3 Converter framework would produce a more elegant solution, but I haven't figured it out yet :) This approach, however, I know works.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With