Binding a map of lists in Spring MVC

19,272

Explanation : if in you controller you have @ModelAttribute("user") User user, and you load a corresponding page that contains <form:form commandName="user">, an empty User is instantiated.

All its attributes are null, or empty in case of a List or a Map. Moreover, its empty lists/maps have been instantiated with an autogrowing implementation. What does it mean ? Let's say we have an empty autogrowing List<Coconut> coconuts. If I do coconuts.get(someIndex).setDiameter(50), it will work instead of throwing an Exception, because the list auto grows and instantiate a coconut for the given index.
Thanks to this autogrowing, submitting a form with the below input will work like a charm :

<form:input path="coconuts[${someIndex}].diameter" />

Now back to your problem : Spring MVC autogrowing works quite well with a chain of objects, each containing a map/list (see this post). But given your exception, it looks like Spring doesn't autogrow the possible objects contained by the autogrowed list/map. In Map<String, List<PrsCDData>> prsCDData, the List<PrsCDData> is a mere empty list with no autogrowing, thus leading to your Exception.

So the solution must use some kind of Apache Common's LazyList or Spring's AutoPopulatingList.
You must implement your own autogrowing map that instantiates a LazyList/AutoPopulatingList for a given index. Do it from scratch or using some kind of Apache Common's LazyMap / MapUtils.lazyMap implementation (so far I have not found a Spring equivalent for LazyMap).

Example of solution, using Apache Commons Collections :

public class PrsData {

  private Map<String, List<PrsCDData>> prsCDData;

  public PrsData() {
      this.prsCDData = MapUtils.lazyMap(new HashMap<String,List<Object>>(), new Factory() {

          public Object create() {
              return LazyList.decorate(new ArrayList<PrsCDData>(), 
                             FactoryUtils.instantiateFactory(PrsCDData.class));
          }

      });
  }

}
Share:
19,272
Umesh Awasthi
Author by

Umesh Awasthi

Updated on June 11, 2022

Comments

  • Umesh Awasthi
    Umesh Awasthi almost 2 years

    I am not sure if this is a complex problem but as a starting person this seems a bit complex to me. I have an object based on which i need to show some values on the UI and let user select some of them, i need to send data back to another controller when user click on the submit button.Here is the structure of my data object

    public class PrsData{
    private Map<String, List<PrsCDData>> prsCDData;
    }
    
    public class PrsCDData{
      private Map<String, Collection<ConfiguredDesignData>> configuredDesignData;
    }
    
    public ConfiguredDesignData{
      // simple fields
    }
    

    I have set the object in model before showing the view like

    model.addAttribute("prsData", productData.getPrData());
    

    In the form i have following settings

    <form:form method="post" commandName="prsData" action="${addProductToCartAction}" >
    <form:hidden path="prsCDData['${prsCDDataMap.key}']
      [${status.index}].configuredDesignData['${configuredDesignDataMap.key}']
      [${configuredDesignDataStatus.index}].code"/>
    
    <form:hidden path="prsCDData['${prsCDDataMap.key}']
      [${status.index}].configuredDesignData['${configuredDesignDataMap.key}']
      [${configuredDesignDataStatus.index}].description"/>
    
    </form:form>
    

    This is what i have at AddProductToCartController

    public String addToCart(@RequestParam("productCodePost") final String code,
    @ModelAttribute("prsData") final PrsData prsData, final Model model,
    @RequestParam(value = "qty", required = false, defaultValue = "1") final long qty)
    

    On submitting the form i am getting following exception

    org.springframework.beans.NullValueInNestedPathException: Invalid property 'prsCDData[Forced][0]' 
    of bean class [com.product.data.PrsData]: 
    Cannot access indexed value of property referenced in indexed property path 'prsCDData[Forced][0]': returned null
    

    It seems like its trying to access the values on this controller while i am trying to send value to that controller and trying to create same object with selected values

    Can any one tell me where i am doing wrong and what i need to take care of

    Edit

    I did some more research and came to know that Spring do not support auto-populating list/map for custom objects and based on the answer i tried to change implementation like

    public class PrsData{
        private Map<String, List<PrsCDData>> prsCDData;
        // lazy init
        public PrsData()
        {
               this.prsCDData = MapUtils.lazyMap(new HashMap<String, List<PrsCDData>>(),
                    FactoryUtils.instantiateFactory(PrsCDData.class));
            }
        }
    
        public class PrsCDData{
          private Map<String, Collection<ConfiguredDesignData>> configuredDesignData;
          public PrsCDData()
        {
    
           this.configuredDesignData = MapUtils.lazyMap(new HashMap<String,  
                                          List<ConfiguredDesignData>>(),
                FactoryUtils.instantiateFactory(ConfiguredDesignData.class));
    
        }
        }
    

    but i am getting following exception

    org.springframework.beans.InvalidPropertyException: 
    Invalid property 'prsCDData[Forced][0]' of bean class [com.data.PrsData]:
    Property referenced in indexed property path 'prsCDData[Forced][0]' 
    is neither an array nor a List nor a Set nor a Map; 
    returned value was [com.data.PrsCDData@6043a24d]
    

    i am not sure which thing i am doing wrong, it seems that either my JSTL expression is not right