Tutorial :Can a Spring form command be a Map?



Question:

Can a Spring form command be a Map? I made my command a Map by extending HashMap and referenced the properties using the ['property'] notation but it didn't work.

Command:

public class MyCommand extends HashMap<String, Object> {  }  

HTML form:

Name: <form:input path="['name']" />  

Results in the error:

org.springframework.beans.NotReadablePropertyException: Invalid property '[name]' of bean class [com.me.MyCommand]: Bean property '[name]' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?  

Is this not allowed or do I have incorrect syntax?


Solution:1

Springn MVC commands need to use JavaBeans naming conventins (ie getXXX() and setXXX()) so no you can't use a map for that.

One alternative is to have a bean with a single Map property ie:

public class MyCommand {    private final Map<String, Object> properties = new HashMap<String, Object>();      public Map<String, Object> getProperties() { return properties; }    // setter optional  }  

Then you can do something like this (not 100% sure on the syntax but it is possible):

Name: <form:input path="properties['name']" />  


Solution:2

Combining the answer of cletus and dbell I could actually make it work and would like to share the solution with you (including binding of values when submitting the form, a flaw reported for cletus solution)

You cannot use directly a map as command, however have another domain object that needs to wrap a lazy map

public class SettingsInformation {        private Map<String, SettingsValue> settingsMap= MapUtils.lazyMap(new HashMap<String, SettingsValue>(),FactoryUtils.instantiateFactory(SettingsValue.class));        public Map<String, SettingsValue> getSettingsMap() {          return settingsMap;      }        public void setSettingsMap(Map<String, SettingsValue > settingsMap) {          this.settingsMap = settingsMap;      }    }  

SettingsValue is a class that actually wraps the value.

public class SettingsValue {    private String value;    public SettingsValue(String value) {      this.value = value;  }      public SettingsValue() {    }    public String getValue() {        return value;  }    public void setValue(String propertyValue) {        this.value = propertyValue;  }  

The controller method providing the model looks like this:

    @RequestMapping(value="/settings", method=RequestMethod.GET)  public ModelAndView showSettings() {        ModelAndView modelAndView = new ModelAndView("settings");        SettingsDTO settingsDTO = settingsService.getSettings();      Map<String, String> settings = settingsDTO.getSettings();        SettingsInformation settingsInformation = new SettingsInformation();        for (Entry<String, String> settingsEntry : settings.entrySet()) {          SettingsValue settingsValue = new SettingsValue(settingsEntry.getValue());          settingsInformation.getSettingsMap().put(settingsEntry.getKey(), settingsValue);      }        modelAndView.addObject("settings", settingsInformation);        return modelAndView;  }  

Your form should look like this

<form:form action="${actionUrl}" commandName="settings">          <form:input path="settingsMap['exampleKey'].value"/>          <input type="submit" value="<fmt:message key="settings.save"/>"/>  </form:form>  

The controller method handling form submission works as usual

@RequestMapping(value="/settings", method=RequestMethod.POST)  public ModelAndView updateSettings(@ModelAttribute(value="settings") SettingsInformation settings) {  [...]  }  

I verified the SettingsInformation bean is actually filled with the values in the form.

Thanks for helping me with this one; If you have any questions feel free to ask.


Solution:3

Ok i have a solution that works for me i use MapUtils.lazyMap

//My Root domain

    public class StandardDomain {            private Map<String, AnotherDomainObj> templateMap= MapUtils.lazyMap(new HashMap<String, AnotherDomainObj>(),FactoryUtils.instantiateFactory(AnotherDomainObj.class));            public Map<String, AnotherDomainObj> getTemplateContentMap() {              return templateMap;          }            public void setTemplateContentMap(Map<String, AnotherDomainObj > templateMap) {              templateMap = templateMap;          }        }  

//my second domain

public class AnotherDomainObj  {        String propertyValue="";            public String getPropertyValue() {             return propertyValue;          }            public void setPropertyValue(String propertyValue) {             this.propertyValue = propertyValue;       }        }  

//In my JSP

<input type="text" value="testthis" name="templateMap['keyName'].propertyValue"/>  


Solution:4

Yes it can but... You need to reference it as <form:input path="name[index].key|value"/> e.g.

<form:input path="name[0].value"/>


Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Previous
Next Post »