Monday, February 1, 2016

Submitting HashMap from JSP with JSTL in Spring framework 4.2.1

The Controller I'm currently working on has this model that it uses, let's just say it is
public class RecipeModel implements Serializable {
  private String recipeName; //not important
  private String authorName; //not important
  private Map<String, Ingredient> ingredients;
  //get and set methods
}

And the Ingredient class of course,
public class Ingredient implements Serializable {
  private String ingredientName;
  private String preparationStyle;
  private String useAtStage;
  //get and set methods
}

Obviously, the above are placeholder examples I'm using, in place of the actual objects I have, so while the scenario may not be ideal, it is just meant to describe the situation.

The JSP managed to display (via JSTL) the model values without a hitch:
<c:forEach items="${recipeModel.ingredients}" var="ingredientMap">
  <input name="ingredientMap['${ingredientMap.key}']" value="${ingredientMap.value.ingredientName}" />
</c:forEach>

The problem arose when I needed to return the amended changes back to the server. The solutions I found seemed either too outdated or tedious. It also didn't help that I had mispelt ingredientmap with a lowercase 'M' either.

What worked?

<input name="ingredientMap['${ingredientMap.key}']" value="${ingredientMap.value.ingredientName}" />
<input name="ingredientMap['${ingredientMap.key}'].useAtStage" value="${ingredientMap.value.useAtStage}" />
<input name="ingredientMap['${ingredientMap.key}'].preparationStyle" value="${ingredientMap.value.preparationStyle}" />

Append the property name (don't forget the dot) after the bracket for the map. Spring is able to translate each field accordingly into the corresponding property of the value object in the map.

That was it. No CustomMapEditor to add in the InitBinder, no mapping of spring:binder stuff anywhere else. Mistakes were made. Mischief managed.

Thursday, January 28, 2016

Spring Framework model naming

It's been 3 months since I'd encountered any quirks! And I've been working on something new!

If you just want the TL;DR, skip to the bottom.

This time round, I came across a weird situation whereby Spring is unable to provide me a model name I'd expect on my JSP. The object was created as "WTSoupModel" (names have been changed, but the pattern remains the same), and you can probably see the issue immediately if you're somewhat familiar with this situation.

The MVC I've been building up had been mostly running smoothly thus far, until this. The controllers, models, and services have been annotated similarly to my previous modules. What went wrong was when I tried accessing this model that was provided by my Controller via the JSP.

I was expecting the name of the model accessible by the JSP to be "wtSoupModel", but it would always return true whenever I test with ${wtSoupModel eq null} until I dug deep enough. Initially, for the sake of the experiment, I added in the model from the sub-module I'd just completed, ReportModel reportModel, and of course, ${reportModel eq null} gave me false, as expected. Then I made sure the WTSoupModel was Serializable. And then I tried renaming it to WanTonSoupModel in full. Aha! It turned out that ${wanTonSoupModel eq null} was false. This means the issue was with the naming, because there wasn't much else to rule out.

Next, I proceeded to find out if there's anyway I could custom the model naming that reaches the JSP. Upon further investigation, I found this that led to this. Experimenting with it in my Controller proved it true.

Final fix (names have been changed):

@RequestMapping(value = "/wantonsoup", method = RequestMethod.GET)
public String viewWTSoupList(Locale locale,
   @ModelAttribute("wtSoupModel") WTSoupModel wtSoupModel,
    ...
   Model model) throws MSBException {
...
}

I still haven't figured out what Spring resolved the original WTSoupModel into by default. Let me know if you do.

Monday, October 5, 2015

Using CascadeType with Hibernate Reverse Engineering Tool

As of Hibernate (version 4.3.11-Final) it seems, silly little me was trying to figure out how to annotate the entity beans with the cascade instruction. I didn't think it made sense to edit the PojoPropertyAccessor.ftl and the likes of those templates for this, much less manually edit the sources after they are generated. It was actually simpler than that, but maybe so straightforward nobody in recent years (up to 2013) bothered to describe how this works.

        <foreign-key constraint-name="fk_1_2" foreign-table="tbl_2">
            <column-ref local-column="id" foreign-column="id"/>
            <one-to-one property="a" cascade="merge" fetch="select"/>
        </foreign-key>

The Cascade will be annotated accordingly with your (all-lowercase) selection from either org.hibernate.annotations.CascadeType or javax.persistence.CascadeType; same difference.