Can I handle Jackson UnrecognizedPropertyException for a #RequestBody parameter? How can I configure this?
I'm working on a spring MVC project, and I use jackson as json plugin. Any mis-spell of the field name in a json request will lead to a error page, which should be a json string consist of error message. I'm a newbie to spring, and I think this error handling can be done with some spring configuration, but failed after several attempts. Any help?
Here is my mvc configure:
#EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#Bean
public ViewResolver resolver() {
InternalResourceViewResolver bean = new InternalResourceViewResolver();
return bean;
}
#Override
public void configureDefaultServletHandling(
DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
My controller:
#RequestMapping(value = "/Login", method = RequestMethod.POST,
consumes="application/json", produces = "application/json")
public #ResponseBody AjaxResponse login(
#RequestBody UserVO user, HttpServletRequest request) {
//do something ...
}
Normal request json is:
{"Username":"123123", "Password":"s3cret"}
But if I send the following request:
{"Username":"123123", "pwd":"s3cret"}
which field name is mis-spell, then Spring catch this UnrecognizedPropertyException, and returned a error page, but I want to catch this exception and return a json string. How can I achieve this?
Use #ExceptionHandler annotation. Some documentation about it: http://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
#Controller
public class WebMvcConfig {
#RequestMapping(value = "/Login", method = RequestMethod.POST,
consumes="application/json", produces = "application/json")
public #ResponseBody AjaxResponse login(#RequestBody UserVO user, HttpServletRequest request) {
//do something ...
}
#ExceptionHandler(UnrecognizedPropertyException.class)
public void errorHandler() {
// do something. e.g. customize error response
}
}
Related
I am migrating my project from springMVC to springboot. One of the controller has an API returning in this way.
I am trying to return JSONObject,
Here is my interface:
public class myController{
#RequestMapping(value = "/api", method = { RequestMethod.GET,
RequestMethod.POST })
public #ResponseBody JSONObject myfunction(HttpServletRequest request,
HttpServletResponse response);
}
I get the the following error:
No converter found for return value of type: class org.json.JSONObject
I have added jackson dependencies. I want to return JSONObject only. I have seen solutions but they are advising me to convert to a string. But i cannot do that as this could affect when i am returning to the front end(Which has been written already in jsp and jQuery and is expecting a JSONObject.)
How do i solve this?
Thanks.
You can use ObjectNode of Jackson library to keep JSONObject structure refer link. For that you have to autowire ObjectMapper in your service
public class myController{
#Autowired
private ObjectMapper jacksonObjectMapper;
#GetMapping
public ObjectNode sayJSONObject() {
ObjectNode objectNode = jacksonObjectMapper.createObjectNode();
objectNode.put("key", "value");
return objectNode;
}
}
After extensive investigations, I wanted to share the problem and the resolution.
Problem
I have a RestController that works well, as long as I'm in charge of converting the JSON message. The moment I try to use an HttpMessageConverter to make the conversion more elegant, the client will start receiving HTTP 406.
So this works:
#RequestMapping(value = "/objects", method = RequestMethod.GET)
public Map<String, Object>[] getObjects(#RequestBody Object jsonQuery) {
MyQuery query = new MyConverter().convert(jsonQuery);
// do something with query
}
But, when I configure the converter, like this:
#Configuration
#EnableWebMvc
#ComponentScan
public class WebConfiguration extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> httpMessageConverters) {
httpMessageConverters.add(new QueryMessageConverter(new MediaType("application", "json")));
}
}
This causes HTTP 406:
#RequestMapping(value = "/objects", method = RequestMethod.GET)
public Map<String, Object>[] getObjects(#RequestBody Query Query) {
// do something with query
}
My pom.xml only refers spring-boot, and doesn't mention jackson at all.
Solution
See below
The solution is really very simple, and it is to register the jackson handler explicitly:
#Configuration
#EnableWebMvc
#ComponentScan
public class WebConfiguration extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> httpMessageConverters) {
httpMessageConverters.add(new QueryMessageConverter(new MediaType("application", "json")));
httpMessageConverters.add(new MappingJackson2HttpMessageConverter());
}
}
I have Implemented a Restful Webservice which produces JSON as my response by anotating #Produces("application/json")
Am Handling application errors with error codes. So i form an object with error code and error message. So when ever there is an application exception, it will produces in XML format. How can produce those in JSON format?
Yes You Can. Catch your Exception and handle it with your custom exception class and throw the exception class by setting response content type as application/json
try{
InitialContext ctx = new InitialContext();
TestIntf intf = (TestIntf)ctx.lookup("java:comp/env/ejb/TestImpl");
}catch(NamingException namingException) {
CustomException lookupException = new CustomException("SYSJNDIE001", namingException.getMessage());
throw lookupException;
}
And your Exception class would look like this
public class CustomException extends Exception {
private static final long serialVersionUID = -5358934896070563181L;
public CustomException(String errorCode, String message) {
super(errorCode, message);
}
}
Create a Provider class Mapper for Exception
#Provider
public class FaultResponseMapper implements ExceptionMapper<CustomException> {
#Context
private HttpHeaders headers;
#Override
public Response toResponse(CustomException customException) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(customException)
.type(MediaType.APPLICATION_JSON).build();
}
}
I am new to Mockito as well as Spring's RestTemplate. I am working on JUnit tests for a functionality which sends a request to a web-service and gets the response through the use of RestTemplate. I want the server to respond with a response that i want so that i can test the functionalities based on this response. I am using Mockito for mocking.
I am not sure where I am going wrong. Am I not creating proper mocks? Is my JSON object mapper not been configured properly?
Configuration file defining the RestTemplate bean:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
<bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"/>
<bean class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
<property name="marshaller" ref="xsStreamMarshaller" />
<property name="unmarshaller" ref="xsStreamMarshaller" />
</bean>
</list>
</property>
</bean>
<bean id="xsStreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller"></bean>
</beans>
My DTO's:
import org.codehaus.jackson.annotate.JsonWriteNullProperties;
#JsonWriteNullProperties(false)
public abstract class BaseDTO {
protected boolean error;
public boolean isError() {
return error;
}
public void setError(boolean error) {
this.error = error;
}
}
public class ChildDTO extends CommercialBaseDTO {
private String fullName;
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
}
Class containing the method to test:
package com.exmpale.mypackage;
import org.springframework.web.client.RestTemplate;
#Component
public class MyUtilClass {
#Autowired
private RestTemplate restTemplate;
public RestTemplate getRestTemplate(){
return restTemplate;
}
public void setRestTemplate(RestTemplate restTemplate){
this.restTemplate = restTemplate;
}
// Method to test
public ChildDTO getChildDTO(MyUser myUser, HttpServletRequest request, HttpServletResponse response)
{
response.setContentType("application/json");
//Nothing much here, it takes the myUser and convert into childDTO
ChildDTO childDTO = new MyUtilClass().getDTOFromUser(request, myUser);
//This is the restTemplate that iam trying to mock.
childDTO = restTemplate.postForObject("http://www.google.com", childDTO, ChildDTO.class);
if (childDTO.isError()) {
//Then do some stuff.........
}
return childDTO;
}
}
The JUnit test class
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations={"test-config.xml"})
public class MyUtilClassTest {
#InjectMocks
RestTemplate restTemplate= new RestTemplate();
private MockRestServiceServer mockServer;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
//Creating the mock server
//Add message conveters
List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
messageConverters.add(new FormHttpMessageConverter());
messageConverters.add(new StringHttpMessageConverter());
messageConverters.add(new MappingJacksonHttpMessageConverter());
//Create Object mapper
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure( DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure( SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false);
objectMapper.configure( SerializationConfig.Feature.AUTO_DETECT_FIELDS, true);
objectMapper.configure( SerializationConfig.Feature.AUTO_DETECT_GETTERS,true);
objectMapper.configure( SerializationConfig.Feature.AUTO_DETECT_IS_GETTERS,true);
MappingJacksonHttpMessageConverter jsonMessageConverter = new MappingJacksonHttpMessageConverter();
jsonMessageConverter.setObjectMapper(objectMapper);
messageConverters.add(jsonMessageConverter);
//Set the message converters
restTemplate.setMessageConverters(messageConverters);
mockServer = MockRestServiceServer.createServer(restTemplate);
}
#Test
public void testGetChildDTO()throws Exception {
MyUtilClass myUtil = new MyUtilClass();
MyUser myUser = new MyUser();
HttpServletRequest request = new HttpServletRequestWrapper(new MockHttpServletRequest());
HttpServletResponse response = new HttpServletResponseWrapper(new MockHttpServletResponse());
//create the mocks for ChildDTO. I want MyUtilClass().getDTOFromUser(request, myUser) to return this.
ChildDTO childDTOMock_One = Mockito.mock(ChildDTO);
//Want this to be returned when restTemplate.postForObject() is called.
ChildDTO childDTOMock_Two = Mockito.mock(ChildDTO.class);
childDTOMock_Two.setError(false);
//create the mocks for userMgntUtils
MyUtilClass myUtilClassMock = Mockito.mock(MyUtilClass.class);
//stub the method getDTOFromUser() to return the mock object. I need this mock to be passed to 'postForObject()'
Mockito.when(myUtilClassMock.getDTOFromUser(request, myUser)).thenReturn(childDTOMock_One);
String responseJSON="{\"error\":false}";
//set the expectation values for mockServer
mockServer.expect( requestTo("http://www.google.com")).andExpect(method(HttpMethod.POST)).andRespond(withSuccess(responseJSON,MediaType.APPLICATION_JSON));
//set the expectation values for restTemplate
Mockito.when(restTemplate.postForObject( "http://www.google.com", childDTOMock_One, ChildDTO.class)).thenReturn(childDTOMock_Two);
TypedUserDTO result = userMgmtUtils.getUserProfileDTO(registerUser, request, response, action);
assertNotNull(result);
}
}
Getting the following exception:
org.springframework.http.converter.HttpMessageNotWritableException:
Could not write JSON: No serializer found for class
org.mockito.internal.stubbing.defaultanswers.GloballyConfiguredAnswer
and no properties discovered to create BeanSerializer (to avoid
exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) )
(through reference chain:
com.biogenidec.dto.TypedUserDTO$$EnhancerByMockitoWithCGLIB$$bee3c447["callbacks"]->org.mockito.internal.creation.MethodInterceptorFilter["handler"]->org.mockito.internal.handler.InvocationNotifierHandler["mockSettings"]->org.mockito.internal.creation.settings.CreationSettings["defaultAnswer"]);
nested exception is org.codehaus.jackson.map.JsonMappingException: No
serializer found for class
org.mockito.internal.stubbing.defaultanswers.GloballyConfiguredAnswer
and no properties discovered to create BeanSerializer (to avoid
exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) )
(through reference chain:
com.biogenidec.dto.TypedUserDTO$$EnhancerByMockitoWithCGLIB$$bee3c447["callbacks"]->org.mockito.internal.creation.MethodInterceptorFilter["handler"]->org.mockito.internal.handler.InvocationNotifierHandler["mockSettings"]->org.mockito.internal.creation.settings.CreationSettings["defaultAnswer"])
And:
Caused by: org.codehaus.jackson.map.JsonMappingException: No serializer found for class org.mockito.internal.stubbing.defaultanswers.GloballyConfiguredAnswer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: com.biogenidec.dto.TypedUserDTO$$EnhancerByMockitoWithCGLIB$$bee3c447["callbacks"]->org.mockito.internal.creation.MethodInterceptorFilter["handler"]->org.mockito.internal.handler.InvocationNotifierHandler["mockSettings"]->org.mockito.internal.creation.settings.CreationSettings["defaultAnswer"])
The idea of Mockito is to test the class and none of the dependencies outside of it. So if your testing MyUtilClass you want to mock the RestTemplate class. And your #InjectMocks is on the wrong class see below.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations={"test-config.xml"})
public class MyUtilClassTest
{
#Mock
private RestTemplate restTemplate;
#InjectMocks
private MyUtilClass myUtilClass;
#Before
public void setUp() throws Exception
{
MockitoAnnotations.initMocks(this);
}
#Test
public void testGetChildDTO()throws Exception
{
MyUser myUser = new MyUser();
HttpServletRequest request = new HttpServletRequestWrapper(new MockHttpServletRequest());
HttpServletResponse response = new HttpServletResponseWrapper(new MockHttpServletResponse());
Mockito.when(RestTemplate.postForObject(Mockito.eq("http://www.google.com",
Mockito.any(ChildDTO.class), Mockito.eq(ChildDTO.class)))).thenAnswer(
new Answer<ChildDTO>()
{
#Override
public ChildDTO answer(InvocationOnMock invocation) throws Throwable
{
//The below statement takes the second argument passed into the method and returns it
return (ChildDTO) invocation.getArguments()[1];
}
});
ChildDTO childDTO = myUtilClass.getDTOFromUser(request, myUser);
//then verify that the restTemplate.postForObject mock was called with the correct parameters
Mockito.verify(restTemplate, Mockito.times(1)).postForObject(Mockito.eq("http://www.google.com",
Mockito.eq(childDTO), Mockito.eq(ChildDTO.class));
}
}
Also I find it bad practice to test other frameworks classes, more often then not they already tested their class and your just duplicating their work.
As correctly noted above, to test your method with mockito, it is not necessary to initialize restTemplate.
It is enough to verify that the parameters of the input are correct (if needed) and return the correct mocking object from restTemplate.
We do not test the restTemplate here, we only test our code. This is the purpose of unit tests.
You can do something like this, or something simpler:
#RunWith(value = MockitoJUnitRunner.class)
public class Test {
#InjectMocks
private MyUtilClass testObj;
#Mock
private RestTemplate restTemplate;
#Mock
MyUser myUser;
#Mock
HttpServletRequest request;
#Mock
HttpServletResponse response;
#Test
public void test() throws Exception {
//Configure sample to comparison and verification the result of the method:
ChildDTO sample = getSample();
//configure mocks:
ChildDTO myObject = new ChildDTO();
//configure myObject properties
ResponseEntity<ChildDTO> respEntity = new ResponseEntity<>(
myObject, HttpStatus.ACCEPTED);
when(restTemplate.postForObject(anyString(), Matchers.<HttpEntity<?>>any(),
Matchers.any(Class.class))).thenReturn(respEntity);
//other stuff to configure correct behaviour of mocks request, response e.t.c.
//act:
ChildDTO result = testObj.getChildDTO(myUser, request, response);
//verify that correct parameters were passed into restTemplate method "postForObject":
verify(restTemplate).postForObject(eq("http://www.google.com"), Matchers.<HttpEntity<?>>any(),
eq(ChildDTO.class)).thenReturn(respEntity);
//assert to verify that we got correct result:
assertEquals(sample, result);
}
}
In my webapp, all my message converters are in place, and if I change getContent below to return a bean/pojo, it returns as " application/json;charset=UTF-8", which is expected, but I now want to serve JSON "as is".
E.g. I have a simple stub web service with which users can PUT a blob of JSON content which is persisted somewhere, and then an equivalent GET call to read it back.
#Controller
public class StubController {
#Autowired
#Qualifier("keyValueStore")
private KVStore kv;
#RequestMapping(value = "/stub/{id}", method = RequestMethod.GET)
public #ResponseBody
String getContent(#PathVariable("id") final String id) {
return kv.get(id);
}
#RequestMapping(value = "/stub/{id}", method = RequestMethod.PUT)
public String putContent(#PathVariable("id") final String id, #RequestBody String body) {
kv.set(id, body);
return "redirect:/stub/"+id;
}
}
However, the getter returns header "Content-Type: text/html;charset=UTF-8" if I call http://host/stub/123.json in the browser. My guess that this is happening is because I'm not returning anything that is "converted" by the Jackson converter, hence the return header isn't modified.
I need it to be application/json -- any ideas what to do? Perhaps an annotation with which I can specify the return headers?
I managed to get around this by adding an HttpServletResponse param to my getContent() method and setting the content type directly.
http://forum.springsource.org/showthread.php?t=97140
#RequestMapping(value = "/stub/{id}", method = RequestMethod.GET)
public #ResponseBody String getContent(#PathVariable("id") final String id, HttpServletResponse response) {
response.setContentType("application/json");
return kv.get(id);
}