Spring 3.0.5 - Adding #ModelAttribute to handler method signature results in JsonMappingException - json

I'm not sure whether this is a misconfiguration on my part, a misunderstanding of what can be accomplished via #ModelAttribute and automatic JSON content conversion, or a bug in either Spring or Jackson. If it turns out to be the latter, of course, I'll file an issue with the appropriate folks.
I've encountered a problem with adding a #ModelAttribute to a controller's handler method. The intent of the method is to expose a bean that's been populated from a form or previous submission, but I can reproduce the issue without actually submitting data into the bean.
I'm using the Spring mvc-showcase sample. It's currently using Spring 3.1, but I first encountered, and am able to reproduce, this issue on my 3.0.5 setup. The mvc-showcase sample uses a pretty standard servlet-context.xml:
servlet-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xsi:schemaLocation="
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
<!-- Enables the Spring MVC #Controller programming model -->
<annotation-driven conversion-service="conversionService">
<argument-resolvers>
<beans:bean class="org.springframework.samples.mvc.data.custom.CustomArgumentResolver"/>
</argument-resolvers>
</annotation-driven>
<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources/ directory -->
<resources mapping="/resources/**" location="/resources/" />
<!-- Resolves views selected for rendering by #Controllers to .jsp resources in the /WEB-INF/views directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<!-- Imports user-defined #Controller beans that process client requests -->
<beans:import resource="controllers.xml" />
<!-- Only needed because we install custom converters to support the examples in the org.springframewok.samples.mvc.convert package -->
<beans:bean id="conversionService" class="org.springframework.samples.mvc.convert.CustomConversionServiceFactoryBean" />
<!-- Only needed because we require fileupload in the org.springframework.samples.mvc.fileupload package -->
<beans:bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" />
</beans:beans>
The controllers.xml referenced in the file simply sets up the relevant component-scan and view-controller for the root path. The relevant snippet is below.
controllers.xml
<!-- Maps '/' requests to the 'home' view -->
<mvc:view-controller path="/" view-name="home"/>
<context:component-scan base-package="org.springframework.samples.mvc" />
The test bean which I am attempting to deliver is a dead-simple POJO.
TestBean.java
package org.springframework.samples.mvc.test;
public class TestBean {
private String testField = "test#example.com";
public String getTestField() {
return testField;
}
public void setTestField(String testField) {
this.testField = testField;
}
}
And finally, the controller, which is also simple.
TestController.java
package org.springframework.samples.mvc.test;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
#Controller
#RequestMapping("test/*")
public class TestController {
#ModelAttribute("testBean")
public TestBean getTestBean() {
return new TestBean();
}
#RequestMapping(value = "beanOnly", method = RequestMethod.POST)
public #ResponseBody
TestBean testBean(#ModelAttribute("testBean") TestBean bean) {
return bean;
}
#RequestMapping(value = "withoutModel", method = RequestMethod.POST)
public #ResponseBody
Model testWithoutModel(Model model) {
model.addAttribute("result", "success");
return model;
}
#RequestMapping(value = "withModel", method = RequestMethod.POST)
public #ResponseBody
Model testWithModel(Model model, #ModelAttribute("testBean") TestBean bean) {
bean.setTestField("This is the new value of testField");
model.addAttribute("result", "success");
return model;
}
}
If I call the controller via the mapped path /mvc-showcase/test/beanOnly, I get a JSON representation of the bean, as expected. Calling the withoutModel handler delivers a JSON representation of the Spring Model object associated with the call. It includes the implicit #ModelAttribute from the initial declaration in the return value, but the bean is unavailable to the method. If I wish to process the results of a form submission, for example, and return a JSON response message, then I need that attribute.
The last method adds the #ModelAttribute, and this is where the trouble comes up. Calling /mvc-showcase/test/withModel causes an exception.
In my 3.0.5 installation, I get a JsonMappingException caused by a lack of serializer for FormattingConversionService. In the 3.1.0 sample, the exception is caused by lack of serializer for DefaultConversionService. I'll include the 3.1 exception here; it seems to have the same root cause, even if the path is a bit different.
3.1 org.codehaus.jackson.map.JsonMappingException
org.codehaus.jackson.map.JsonMappingException: No serializer found for class org.springframework.format.support.DefaultFormattingConversionService and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: org.springframework.validation.support.BindingAwareModelMap["org.springframework.validation.BindingResult.testBean"]->org.springframework.validation.BeanPropertyBindingResult["propertyAccessor"]->org.springframework.beans.BeanWrapperImpl["conversionService"])
at org.codehaus.jackson.map.ser.StdSerializerProvider$1.failForEmpty(StdSerializerProvider.java:89)
at org.codehaus.jackson.map.ser.StdSerializerProvider$1.serialize(StdSerializerProvider.java:62)
at org.codehaus.jackson.map.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:272)
at org.codehaus.jackson.map.ser.BeanSerializer.serializeFields(BeanSerializer.java:175)
at org.codehaus.jackson.map.ser.BeanSerializer.serialize(BeanSerializer.java:147)
at org.codehaus.jackson.map.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:272)
at org.codehaus.jackson.map.ser.BeanSerializer.serializeFields(BeanSerializer.java:175)
at org.codehaus.jackson.map.ser.BeanSerializer.serialize(BeanSerializer.java:147)
at org.codehaus.jackson.map.ser.MapSerializer.serializeFields(MapSerializer.java:207)
at org.codehaus.jackson.map.ser.MapSerializer.serialize(MapSerializer.java:140)
at org.codehaus.jackson.map.ser.MapSerializer.serialize(MapSerializer.java:22)
at org.codehaus.jackson.map.ser.StdSerializerProvider._serializeValue(StdSerializerProvider.java:315)
at org.codehaus.jackson.map.ser.StdSerializerProvider.serializeValue(StdSerializerProvider.java:242)
at org.codehaus.jackson.map.ObjectMapper.writeValue(ObjectMapper.java:1030)
at org.springframework.http.converter.json.MappingJacksonHttpMessageConverter.writeInternal(MappingJacksonHttpMessageConverter.java:153)
at org.springframework.http.converter.AbstractHttpMessageConverter.write(AbstractHttpMessageConverter.java:181)
at org.springframework.web.servlet.mvc.method.annotation.support.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:121)
at org.springframework.web.servlet.mvc.method.annotation.support.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:101)
at org.springframework.web.servlet.mvc.method.annotation.support.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:81)
at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:64)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:114)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMethodAdapter.invokeHandlerMethod(RequestMappingHandlerMethodAdapter.java:505)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMethodAdapter.handleInternal(RequestMappingHandlerMethodAdapter.java:468)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:790)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:719)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:644)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:560)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:710)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:803)
at
...
So, is there some configuration I am missing that should allow the Jackson converter to properly handle a response derived from a handler with #ModelAttribute in the method signature? If not, any thoughts as to whether this is more likely a Spring bug or a Jackson bug? I'm leaning toward Spring, at this point.

It looks like a Spring config problem, when serializing to JSON the DefaultFormattingConversionService is empty and Jackson (by default) will throw an exception if a bean is empty see FAIL_ON_EMPTY_BEANS in the features documentation. But I am not clear why the bean is empty.
It should work if you set FAIL_ON_EMPTY_BEANS to false, but still doesn't really explain why it is happening in the first place.
DefaultFormattingConversionService is new to 3.1 - it extends the FormattingConversionService which explains the different exceptions between 3.0.5 and 3.1.
I do not think it is a Jackson problem, although a new version of Jackson (1.8.0) was released only 3 days ago so you could try that also.
I will try to reproduce this locally.

Related

Apache Camel Rest Custom Json Deserializer

I use Camel 2.16.0 for a Camel Rest project. I have introduced an abstract type that I need a custom deserializer to handle. This works as expected in my deserialization unit tests where I register my custom deserializer to the Objectmapper for the tests. To my understanding it is possible to register custom modules to the Jackson Objectmapper used by Camel as well (camel json).
My configuration:
...
<camelContext id="formsContext" xmlns="http://camel.apache.org/schema/spring">
...
<dataFormats>
<json id="json" library="Jackson" useList="true" unmarshalTypeName="myPackage.model.CustomDeserialized" moduleClassNames="myPackage.MyModule" />
</dataFormats>
</camelContext>
My module:
package myPackage;
import com.fasterxml.jackson.databind.module.SimpleModule;
public class MyModule extends SimpleModule {
public MyModule() {
super();
addDeserializer(CustomDeserialized.class, new MyDeserializer());
}
}
The Camel rest configuration:
restConfiguration()
.component("servlet")
.bindingMode(RestBindingMode.json)
.dataFormatProperty("prettyPrint", "true")
.contextPath("/")
.port(8080)
.jsonDataFormat("json");
When running the service and invoking a function that utilize the objectmapper I get the exception:
com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of myPackage.model.CustomDeserialized, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information
Any suggestions on what is wrong with my setup?
I found this solution to the problem and used this implementation for my custom jackson dataformat:
public class JacksonDataFormatExtension extends JacksonDataFormat {
public JacksonDataFormatExtension() {
super(CustomDeserialized.class);
}
protected void doStart() throws Exception {
addModule(new MyModule());
super.doStart();
}
}

getContext() method of CustomContextResolver is not called by Jackson

I am struggling with this issue for days now and have no clue how to solve this. Any quick help will be grateful.
I need to convert LocalDate from JSON string which I am receiving from REST service build using apache CXF and jackson. I wrote custom ContextResolver and registered JavaTimeModule in Mapper object.
When I run the application, default constructor is called, that means it has been loaded, but getContext() method which returns ObjectMapper never gets called.
I have registered same ContextResolver in server and client side.
All dependencies are in place(jackson databind, core, annotation, datatype-jsr310).
I am able to fetch JSON response when I hit REST URI directly in browser. Issue comes when I call same URI annotated method from client code
Below is my client code.
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
#Provider //makes this bean a Provider
public class LocalDateObjectMapperContextResolver implements ContextResolver<ObjectMapper>{
private final ObjectMapper MAPPER;
public LocalDateObjectMapperContextResolver() {
MAPPER = new ObjectMapper();
MAPPER.registerModule(new JavaTimeModule());
MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
}
#Override
public ObjectMapper getContext(Class<?> type) {
return MAPPER;
}
}
<jaxrs:client id="testclient"
serviceClass="package1.RESTService"
username="abc"
password="abc"
address="$serviceURL">
<jaxrs:features>
<bean class="org.apache.cxf.transport.common.gzip.GZIPFeature"/>
<cxf:logging/>
</jaxrs:features>
<jaxrs:providers>
<bean class="org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider"/>
<bean class="mypackage.LocalDateObjectMapperContextResolver"/>
</jaxrs:providers>
</jaxrs:client>
Same way, This contextResolver is registered on server side also under
<jaxrs:server>
.....
<jaxrs:providers>
<bean class="org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider"/>
<bean class="mypackage.LocalDateObjectMapperContextResolver"/>
</jaxrs:providers>
</jaxrs:server>
Any reason why getContext is not called?
I also tried by extending ObjectMapper and registering javaTimeModule there, but dont know how to register customObjectMapper in Jackson flow. I just put default constructor for testing, And it does get called while application startup, but then again, No results, I still get same error.
Error: No suitable constructor found for type [simple type, class java.time.LocalDate]: can not instantiate from JSON object (need to add/enable type information?)
I had exactly the same problem #peeskillet describes in question comment.
I was using Jackson dependencies from version 2 and jackson-jaxrs from version 1.
All solved when moved all dependencies to version 2.
If you are using Maven you can add following two maven dependency.
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
And Add following code snippet.
#Configuration
public class CxfConfig {
#Component
#javax.ws.rs.ext.Provider
public static class JacksonJaxbJsonProvider
extends com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider {
#Autowired
private ObjectMapper objectMapper;
#PostConstruct
public void init() {
objectMapper.registerModule(new Jdk8Module());
}
}
}

Spring Boot Microservice Jackson JSON serialisation NON NULL

I'm currently working on a Spring Boot ( Version 1.3.1 ) Microservice which connects to MongoDB backend and provides the backend data ( Ex: Provider object ) to the client via controller.
The project has got one class file which extends ResourceSupport ( Ex: ProviderResourceSupport ) and also another class which extends ResourceSupportAssembler class ( Ex: ProviderAssembler ) for generating Links to the Response objects.
Ideally my requirement is to customise the JSON objects on a need basis and as such using #JsonView ( followed this link - https://spring.io/blog/2014/12/02/latest-jackson-integration-improvements-in-spring ) and added Spring Jackson dependencies in the maven project.
I have also added spring.jackson.serialization-inclusion=non-null & spring.jackson.serialization.indent_output=true in the application.properties.
For one of the method in the controller, the response will be 'ResponseEntity< List< ProviderResourceSupport>>' , and this method is returning with a 'null' response if the data is not present.
I have added #JsonInclude(Include=NON_NULL) on my entity objects and controllers but still getting the 'null' response.
I don't want the 'null' as the response and request you to help me incase if anyone has faced the similar issue.
I fixed this null properties escaping from json response extending a Jackson Mapper Bean but I don't use Spring Boot, take a quickly look and check if this is suitable for you
public class Jackson2ObjectMapperCustom extends Jackson2ObjectMapperFactoryBean {
#Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
getObject().setSerializationInclusion(JsonInclude.Include.NON_NULL).setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
Hibernate5Module hibernateModule = new Hibernate5Module();
hibernateModule.disable(Feature.USE_TRANSIENT_ANNOTATION);
hibernateModule.enable(Feature.FORCE_LAZY_LOADING);
getObject().registerModules(new JavaTimeModule(), hibernateModule);
getObject().configure(SerializationFeature.INDENT_OUTPUT, true);
getObject().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
getObject().setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"));
}
}
And in my case I use Spring Xml configuration
<bean id="objectMapper" class="com.xxx.common.Jackson2ObjectMapperCustom" />
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
<bean class="org.springframework.http.converter.ResourceHttpMessageConverter"/>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="objectMapper"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>

Spring 4 + #ControllerAdvice: Jsonp support for ResponseBody causes Deployment Exception?

I am adding JSONP support to a REST Service in SPRING4 + JDK 8 + STS 3.6.4
Versions:
Spring 4.1.6.RELEASE
My implementation is based on these links:
http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc-ann-jsonp
The REST service returns ResponseEntity or ResponseBody and use case is to return data in JSONP format.
Added a ControllerAdvice
#ControllerAdvice
public class JsonpCallbackAdvice extends AbstractJsonpResponseBodyAdvice {
public JsonpCallbackAdvice(){
super("Callback");
}
}
Here is the Controller of the REST Service
#Controller
public class AcctController {
...
#RequestMapping(value = "/act/{actNum}", method = RequestMethod.GET)
public ResponseEntity<Account> getAccount(#PathVariable("actNum") Integer accountNum) throws Exception {
...
return new ResponseEntity<account>();
}
Here is the relevant web application context configuration
...
<context:component-scan base-package="com.controllers" />
<bean name="jsonMessageConverter"
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" />
...
The controller and ControllerAdvice are in same package.
When deployment of the project is initiated following exception is observed
Caused by: java.lang.ClassCastException: [Ljava.lang.String; cannot be cast to java.lang.String
at org.springframework.context.annotation.AnnotationBeanNameGenerator.determineBeanNameFromAnnotation(AnnotationBeanNameGenerator.java:91)
at org.springframework.context.annotation.AnnotationBeanNameGenerator.generateBeanName(AnnotationBeanNameGenerator.java:69)
at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(ClassPathBeanDefinitionScanner.java:246)
at org.springframework.context.annotation.ComponentScanBeanDefinitionParser.parse(ComponentScanBeanDefinitionParser.java:84)
at org.springframework.beans.factory.xml.NamespaceHandlerSupport.parse(NamespaceHandlerSupport.java:74)
at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1427)
at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1417)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:174)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:144)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.registerBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:100)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.registerBeanDefinitions(XmlBeanDefinitionReader.java:510)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:392)
This exception was not happening before the ControllerAdvice was added.
Also, it does not happens when #ControllerAdvice annotation is removed
or
the component scan excludes scanning package of the ControllerAdvice class
I tried with Spring version 4.2.0.RC1, and the exception still happens.
Kindly help with resolution of this exception, since not much help is available online.
It seems like a bug in SPRING 4, however am not sure.

Spring MVC RESTful web services with Jackson: How to restrict to subset of URLs

Following the steps outlined here http://www.journaldev.com/2552/spring-restful-web-service-example-with-json-jackson-and-client-program , I've been able to setup my RESTful WS exactly how I want it.
Now, I want to make this app context a hybrid of RESTful WS (under a specific URL prefix /myapp/rest/) and standard MVC mappings for the rest of the site.
But from the servlet-context XML file, I can't really make out how to restrict MappingJackson2HttpMessageConverterto only apply to a certain URL prefix.
<!-- Configure to plugin JSON as request and response in method handler -->
<beans:bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<beans:property name="messageConverters">
<beans:list>
<beans:ref bean="jsonMessageConverter"/>
</beans:list>
</beans:property>
</beans:bean>
<!-- Configure bean to convert JSON to POJO and vice versa -->
<beans:bean id="jsonMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
</beans:bean>
Is there anything else that I should be checking on here?
Thanks
Wong
You can have both SpringMVC style controllers and SpringMVC REST style controllers in the same application. In fact you can write one SpringMVC handler method and a REST Endpoint in same controller too.
#Controller
public class MyController
{
#RequestMapping("/home")
public String homePage(Model model)
{
model.addAttribute("data",someObject);
return "home"; //this will render the view home.jsp
}
#RequestMapping(value="/users", method=RequestMethod.POST)
#ResponseBody
public User saveUser(#RequestBody User user)
{
//persist user
return user; //this will return JSON/XML based representation of User object
}
}
Observe the use of #RequestBody and #ResponseBody annotations for REST style handler methods.