My rendezvous with Message Converters

Recently I have been exploring Spring @ResponseBody. My objective was to create a Handler Method, which will return content depending on the Accept header sent along with the request, i.e. if the request has application/json in the Accept header then the content returned will be in json format, if there is application/xml in the accept header then the content returned will be in xml format and so on.

So in order to fulfill my objective, I annotated the Handler method with @ResponseBody, and using <mvc:annotation-driven/> in the Spring Configuration, automatically registers all the default HttpMessageConverters in the Application Context.

So with all of my configurations in place, when I tried to invoke the Handler method (with Accept Header - application/json) , I surprisingly got error code 406 - Not Acceptable. But I could not understand the issue behind this case as all the configurations are in place, and the HttpMessageConverters are not getting kicked in when the Object is being returned from my Handler method.

So in order to go into the root of the problem, I enabled Spring logging (org.springframework=debug) and got the following

My request URL has the format *.htm and this is the Spring Dispatcher Servlet mapping URL for my application. So, the request is going to use content negotiation (for the extension) first before checking the value of an Accept header. This is default behaviour and with an extension like *.htm, Spring will use org.springframework.web.accept.ServletPathExtensionContentNegotiationStrategy and resolve that the acceptable media type to return is text/html which does not match with the Media Type present in the Accept Header ie. application/json and therefore 406 is returned.

So to resolve this issue, either we need to use a different and separate URL mapping which doesn't have extensions like .htm or change the Order of ContentNegotiationStrategy, so that the ContentNegotiationStrategy based on the Request Header comes in Operation first rather than the one which resolves based on the extension in the request URL.

The first solution is quick and simple to implement so I opted for that, and the json Content is now accessible.

Now I made the Accept Header to contain application/xml, but this time again surprisingly I got again 406 - Not Acceptable. I found that the bean that I was returning from my Handler method do not have @XmlRootElement annotation. So I annotated the Bean class with it along with @XmlAccessorType with the value XmlAccessType.NONE. This annotation gives us the benefit that which fields needs to be serialized by JAXB and which not, beacuse if we do not use the annotation the bean declaration may contain interfaces, in this case JAXB will fail to serialize. So It is better to use this annotation and then indivudually annotate each field with @XmlElement or @XmlAttribute or other annotations accordingly. Another approach is not to use @XmlAccessorType with the value XmlAccessType.NONE and rather annotate field which are to not to be part of the serialization process with @XmlTransient. However the main drawback of this approach is for the inherited field, for all Groovy beans there is MetaClass type in each and every bean as all Groovy classes implements GroovyObject, and JAXB fails for this Type as it a Interface.
So I have opted for the first approach and got the content in xml format.

My next challenge is to resolve for pdf and excel format, and I cannot find any predefined HttpMessageConverters for this types, so it can be resolved either by writing Custom HttpMessageConverters for the desired Media Type and then registering them in the Application Context (<mvc:annotation-driven> <mvc:message-converters> <<Specify All Bean classes for CustomHttpMessageConverters here>></mvc:message-converters></mvc:annotation-driven>) or by converting the bean to the desired format(Document bean of com.lowagie.iText OR Worksheet bean of poi api and then writing the bean to the File with the help of FileOutputStream) in the code block and then writing the content (of the file) to the servlet output stream so that they are available for download by setting the response Content Type header to application/octet-stream and also setting the response header to Content Disposition. I opted for the second option.

Please note that in the Handler method, I have annotated a parameter with @RequestHeader for Accept header, so that I can fetch the value and do the processing accordingly.

Now for Downloading the .pdf or .xls file we can also use Spring FileSystemResource.

Just we have to return the File written (as describe above) with FileOutputStream with FileSystemResource, the FileSystemResource. will make the file available for download in the browser, but the main catch here is that with for .pdf file the PDF file opens directly in the browser, but for .xls file, the FileSystemResource provides a file of unknown format available for download in the browser, so in order to get the .xls file for download we need to set the response header for Content Type and for Content Disposition, and then the file will be available for download in the desired format, when we return an Object of FileSystemResource wrapping the spreadsheet.

Please feel free to share your Views and Feedback and Till next time Keep Coding and Keep experiencing.....

Comments

Popular posts from this blog

Use of @Configurable annotation.

Spring WS - Part 4

Spring WS - Part 5