Tuesday, February 16, 2016

Spring framework has issues with periods

I didn't know what it was initially, but I found stuff like this, this and this.

The one other thing that stood out to me was when I moused over @RequestMapping in Eclipse and one of the list of arguments was noted as such:
  • @PathVariable annotated parameters (Servlet-only) for access to URI template values (i.e. /hotels/{hotel}). Variable values will be converted to the declared method argument type. By default, the URI template will match against the regular expression [^\.]* (i.e. any character other than period), but this can be changed by specifying another regular expression, like so: /hotels/{hotel:\d+}.
 Simply put, if you URL ends with a period "." then Spring defaults to assuming it could be a file extension, and will strip off anything after the dot and return whatever string preceding it.

My annotation went from

@RequestMapping(value = "/fruits/{fruitName}", method = RequestMethod.GET)

Into inclusion of a simple regex that factors in the period symbol

@RequestMapping(value = "/fruits/{fruitName:.+}", method = RequestMethod.GET)

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.