Monday, August 26, 2013

How To create a global InitBinder in Spring with @ControllerAdvice

Date formatting could be an annoying problem when dealing with your views. I wrote an article about it when I was facing Json problems (see How To format dates in Json within Spring 3) but, as you may know, even if you don't need to use Json in your views, you'll probably have to face some troubles with date formats.

Sometimes you can avoid all your problems simply using the specific JSTL fmt in your jsp (see references). But what to do if you can't use JSTL in your jsp, or if a simple JSTL won't solve your problems?

Someone suggests to inject an InitBinder in your controller. What an InitBinder does is to grab your data from web request parameters and bind it to your JavaBean objects. So this could be a good solution if you have to deal with different kinds of data inputs in different controllers. But what if you have to always deal, for example, with dates? You could write a specific InitBinder for each of your controllers, but this is not a good procedure if you want to maximize your code efficiency by an accurate modularization (and this should be your aim, since you're using MVC paradigm).

What I suggest you to do is to create something like a single global InitBinder, which will be activated everytime your application needs it, without any controller restriction. There's a really simple way to do so, using Spring @ControllerAdvice annotation. A class annotated with @ControllerAdvice is a class that assists every controller, and it's easily autodetected through Spring classpath scanning.

If this is your case, and you want to create an InitBinder which manages your dates parsing them in (for example) dd/MM/yyyy format, here's what you can do.

The steps!
  1. Create a DateEditor class like this:
    public class DateEditor extends PropertyEditorSupport {
     
     public void setAsText(String value) {
            try {
                setValue(new SimpleDateFormat("dd/MM/yyyy").parse(value));
            } catch(ParseException e) {
                setValue(null);
            }
        }
    
        public String getAsText() {
         String s = "";
         if (getValue() != null) {
       s = new SimpleDateFormat("dd/MM/yyyy").format((Date) getValue());
      }
         return s;
        }
    
  2. Create a class annotated with @ControllerAdvice (I called it GlobalBindingInitializer):
    @ControllerAdvice
    public class GlobalBindingInitializer {
     
     /* Initialize a global InitBinder for dates instead of cloning its code in every Controller */
     
     @InitBinder
     public void binder(WebDataBinder binder) {
      binder.registerCustomEditor(Date.class, new DateEditor());
     }
    }
    
  3. In your Spring MVC configuration file (for example webmvc-config.xml) add the lines that allow Spring to scan the package in which you created your GlobalBindingInitializer class. For example, if you created GlobalBindingInitializer in the org.example.common package:
    <context:component-scan base-package="org.example.common" />
    

There's nothing else to do for your InitBinder date formatter to work. Remember that @ControllerAdvice is an extremely helpful annotation even when you have to handle exceptions, or if you want to create a global accessible @ModelAttribute for your application.

References

Monday, August 19, 2013

How To display trailing zeros in Json within Spring 3 and Jackson

This post could be simply read as a revisitation of the one in which I explained How To format dates in Json within Spring 3. But since the problem to solve is different, I think it could be useful to write a new guide about it, even if the mechanics are quite the same.

I recently discovered that managing prices with floats is a bad idea, so I decided to change all of my "float price" variables to a more efficient BigDecimal type. I know, the use of BigDecimals is controversial too, but it presently fits my needs, so I decided to use them in my web application.

Anyway, prices types are not what I'm going to talk about now. What I discovered when I tried to put my values in a table obtained via Json, is that Json automatically trims out trailing zeros after the decimal mark. This means that if you have to display a price like 1.20 dollars, you'll probably display 1.2 instead.

So, what I needed was something that would have been able to pick up my BigDecimal from the model, format it in a string including trailing zeros, and pass it to my jsp as Json. All of this, using Spring and Jackson, a Java library for processing JSON.

I assume you've got a jsp (i.e. itemslist.jsp), a controller class (i.e. ItemController.java) and a simple java class for the objects you want to manage in your Json call (e.g. Item.java). Plus, you should have enabled Spring annotation based configuration (see the reference links at the bottom of this post).

The steps!
  1. Create a PriceJsonSerializer class (you can change the name depending on your needs) that extends JsonSerializer. Like this:
    public class PriceJsonSerializer extends JsonSerializer<BigDecimal> {
    
      @Override
      public void serialize(BigDecimal value, JsonGenerator jgen, SerializerProvider provider) 
        throws IOException, JsonProcessingException {
          jgen.writeString(value.setScale(2, BigDecimal.ROUND_HALF_UP).toString());
      }
    }
    

    As you can see, the serialize method writes in a String format the BigDecimal, ensuring that it's scaled in order to display two digits after the decimal mark (you can customize this behaviour changing the setScale parameter "2" to a higher or lower number).

    As you can see, the serialize method sets up the format you want to use for your BigDecimal, writing it in a String format.

    Important: this means that your object will reach the jsp in a String format. So if you want to perform client-side arithmetics calculations on your values (e.g., in javascript) you'll have to parse them back to a number format. For example, if you have two values a = 2 and b = 3 in your jsp, writing a + b will perform an append procedure between strings, not a sum, giving you 32 instead of 5!

  2. In Item.java class, insert the @JsonSerialize annotation above the date getters:
    @JsonSerialize(using=PriceJsonSerializer.class)
    public BigDecimal getPrice() {
     return price;
    }
    
    @JsonSerialize(using=PriceJsonSerializer.class)
    public void setPrice(BigDecimal price) {
     this.price = price;
    }
    

I hope this works for you as it does for me. If not, comment and we'll try to figure out a proper solution. Well, your comments are welcome in any way :)

References

Monday, August 12, 2013

How To Solve JSON infinite recursion Stackoverflow
(with Spring and Jackson annotations)

Infinite recursion picture

Coding in Spring 3 with JPA and Jackson is supposed to semplify your life, sparing you from writing thousand lines of code. Sometimes, though, you'll notice that all these annotations hide the real core of your application, preventing you to really understand what your code is doing (and what it is supposed to do).
This means that if you want to develop a good java application or website, you need to carefully read the docs or (more realistically) you need to understand how to read your error logs and combine them with a well-written search engine query.

This is what I had to do when I first met the problem I'm about to write about.



The problem.

I was getting a wrong JSON response while using a many-to-many relationship, that caused my application to enter in an infinite loop giving as output an infinite recursion Stackoverflow error.

Here is part of what I found in my Eclipse console:

Request processing failed; nested exception is org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError) (through reference chain: [here is the loop]) with root cause
java.lang.StackOverflowError
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:567)
 at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:143)
 at com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serializeContents(CollectionSerializer.java:118)
 at com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serializeContents(CollectionSerializer.java:24)
 at com.fasterxml.jackson.databind.ser.std.AsArraySerializerBase.serialize(AsArraySerializerBase.java:180)
 at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:544)
 at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:551)
 ...

and so on.

Since I was using Datatables jQuery plugin, I also had this output in my browser window:

DataTables warning (table id = 'products'): DataTables warning: JSON data from server could not be parsed. This is caused by a JSON formatting error.

What I noticed is that the same database query, without JSON, was working well. So the problem (as the console in fact said) was with Jackson and Json serialization. As the docs state (see references), bi-directional references for ORM-managed beans (iBatis, Hibernate) would cause serialization to failure since they are cyclic dependencies. Since Jackson 1.6, this problem has been solved by the introduction of two new annotations: @JsonManagedReference and @JsonBackReference (and see the end of this post to give a look at the @JsonIdentityInfo annotation).

How does serialization work? (The solution in theory)
In computer science, in the context of data storage and transmission, serialization is the process of translating data structures or object state into a format that can be stored (for example, in a file or memory buffer, or transmitted across a network connection link) and resurrected later in the same or another computer environment.

For Jackson to work well, one of the two sides of the relationship should not be serialized, in order to avoid the annoying infinite recursive loop that causes our stackoverflow error.

So, Jackson takes the forward part of the reference, for example an attribute of a java class (i.e. List<Role> roles in User class), and converts it in a json-like storage format; this is the so-called marshalling process.
Then, Jackson looks for the back part of the reference (i.e. List<User> users in Role class) and leaves it as it is, not serializing it. This part of the relationship will be re-constructed during the deserialization (unmarshalling) of the forward reference.

The solution in practice.

It's very simple. Assuming that your database query already works without JSON, all you have to do is this:

  1. Add the @JsonManagedReference In the forward part of the relationship (i.e. User.java class):
    @Entity
    public class User implements java.io.Serializable{
     
     @Id
     @GeneratedValue(strategy=GenerationType.IDENTITY)
     private long id;
     
     @Column(name="name")
     private String name;
    
     @ManyToMany
     @JoinTable(name="users_roles",joinColumns=@JoinColumn(name = "user_fk"),
     inverseJoinColumns=@JoinColumn(name = "role_fk"))
     @JsonManagedReference
     private Set<Role> roles = new HashSet<Role>();
    
    ...
    
  2. Add the @JsonBackReference In the back part of the relationship (i.e. Role.java class):
    @Entity
    public class Role implements java.io.Serializable {
    
     @Id 
     @GeneratedValue(strategy=GenerationType.IDENTITY)
     private int id;
    
     @ManyToMany(mappedBy="roles")
     @JsonBackReference
     private Set<User> users = new HashSet<User>();
    
    ...
    

The work is done. If you take a look at your firebug logs, you'll notice that the infinite recursive loop has disappeared.


Edit (02/09/2013)

Another useful annotation you could check is @JsonIdentityInfo: using it, everytime Jackson serializes your object, it will add an ID (or another attribute of your choose) to it, so that it won't entirely "scan" it again everytime. This can be useful when you've got a chain loop between more interrelated objects (for example: Order -> OrderLine -> User -> Order and over again).

In this case you've got to be careful, since you could need to read your object's attributes more than once (for example in a products list with more products that share the same seller), and this annotation prevents you to do so. I suggest to always take a look at firebug logs to check the Json response and see what's going on in your code.


References

Monday, August 5, 2013

How To format dates in Json within Spring 3


After I managed to fix the Datatables jQuery plugin, finally squeezing my "Movie" class into the awesome table I prepared in my jsp, I found out that the dates in the columns were absolutely unintelligible: they were not properly formatted, and so they were showing up as a Long useless number.
My DataTables implementation uses Json to call the informations with which I'll fill the table's columns and rows. So, what I needed was something like a "real-time date formatter" for Json, something that would have been able to pick up my Date object from the model, format it in a comprehensible string format, and pass it to my DataTables plugin as Json.
If this is your situation at the moment, you'll probabily find this guide useful, since it's not very long and not very complicated. Well, it's not complicated at all, actually.

I assume you've got a jsp (i.e. movielist.jsp), a controller class (i.e. MovieController.java) and a simple java class for the objects you want to manage in your Json call (e.g. Movie.java). Oh, you should also have enabled Spring annotation based configuration (see the reference links at the bottom of this post). So...

The steps!


1. Create a DateJsonSerializer class that extends JsonSerializer. Like this:
public class DateJsonSerializer extends JsonSerializer<date> {
 
 @Override
 public void serialize(Date value, JsonGenerator jgen, SerializerProvider provider) 
   throws IOException, JsonProcessingException {
     DateFormat formatter = new SimpleDateFormat("dd-MM-yyyy");
     jgen.writeString(formatter.format(value));
 }
}

As you can see, the serialize method sets up the format you want to use for your date strings.

2. In Movie.java class, insert the @JsonSerialize annotation above the date getters:

@JsonSerialize(using=DateJsonSerializer.class)
public Date getDate_in() {
 return date_in;
}
public void setDate_in(Date date_in) {
 this.date_in = date_in;
}

That's it. Yes, your work should already be done.
If you find something not working, please comment and I'll try to figure out a proper solution.

REFERENCE LINKS:
Spring docs - Annotation type JsonSerialize
Sakae Nakajima - Spring 3 Enable Annotation based Configuration