I am trying to implement a scenario where I want to be able to send an array of JSON object as request body to Spring MVC controller.
I have gone through the following posts
- Custom HttpMessageConverter with @ResponseBody to do Json things
- Howto get rid of <mvc:annotation-driven />?
- http://prasanthnath.wordpress.com/2013/08/23/type-conversion-in-spring/#more-199
- http://gerrydevstory.com/2013/08/14/posting-json-to-spring-mvc-controller/
- Spring-Returning json with @ResponseBody when the Accept header is */* throws HttpMediaTypeNotAcceptableException
But none of the suggestions are working. I apologize if I missed any other posts.
The idea is that there are two controller functions that will
- fetch data from the database The controller will query the database and return a List of objects to be serialized.
- accept the JSON response and convert them to list of objects to be inserted into the database.
The first one works without any explicit serialization on my part.
@RequestMapping("/config")
public class ConfigController {
@Autowired
private final Service service;
// This works. I don't know why.
@RequestMapping("/fetch", method=RequestMethod.GET)
@ResponseBody
@ResponseStatus(HttpStatus.OK)
public String readConfigProperties() throws Exception {
ImmutableList<Config> configObjects = this.service.readConfiguration();
return configObjects;
}
}
I am having trouble taking the JSON response passed in request body and make them available as a list of objects. The controller function seems to be passing a list of linked hashmaps which is not what I want. This is raising a ClassCastException. The function set up is given below
UPDATE: I used @ResponseBody annotation in a previous version of this post. I changed the post to use @RequestBody, but no impact.
@RequestMapping(method=RequestMethod.POST, consumes={"application/json"}, value="/update}
@ResponseStatus(HttpStatus.OK)
public void updateConfig(@RequestBody List<Config> configList) throws Exception {
this.service.updateConfiguration(configList);
}
In this case configList is a list of LinkedHashMap objects and so it causes a ClassCastException to be thrown. I don't know why.
My headers are as follows:
Content-Type: application/json; charset=utf-8
Stack trace:
java.lang.ClassCastException: java.util.LinkedHashMap incompatible with com.kartik.springmvc.model.Config
com.kartik.springmvc.service.ConfigPropertyService.upsertConfigurationProperties(ConfigPropertyService.java:56)
com.kartik.springmvc.controller.ConfigController .upsertConfigurationProperties(ConfigController .java:86)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:88)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:55)
java.lang.reflect.Method.invoke(Method.java:613)
org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:213)
org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:126)
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:96)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:617)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:578)
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:923)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:852)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:882)
org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:789)
javax.servlet.http.HttpServlet.service(HttpServlet.java:646)
My converters and controller specific configuration
<context:annotation-config />
<context:component-scan base-package="com.kartik.springmvc.controller" />
<bean class="com.kartik.springmvc.controller.AppConfigPropertiesConverter" id="appConfigPropertiesConverter"/>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="appConfigPropertiesConverter" />
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
</list>
</property>
</bean>
<mvc:annotation-driven/>
My converter implementation is given below. UPDATE: This class is not invoked.
public class AppConfigPropertiesConverter extends AbstractHttpMessageConverter<Object> {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private Gson gson = new Gson();
/**
* Construct a new {@code GsonHttpMessageConverter}.
*/
public AppConfigPropertiesConverter() {
super(new MediaType("application", "json", DEFAULT_CHARSET), new MediaType(
"application", "*+json", DEFAULT_CHARSET));
}
/** Supports only {@link Config} instances. */
@Override
protected boolean supports(Class<?> clazz) {
// TODO Auto-generated method stub
return true;
}
/**
* Converts to a list of {@Config}
*/
@Override
protected Object readInternal(
Class<?> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
TypeToken<?> token = TypeToken.get(clazz);
System.out.println("#################################################################3");
Reader jsonReader =
new InputStreamReader(inputMessage.getBody(), DEFAULT_CHARSET.displayName());
System.out.println("####################################################################");
try {
return this.gson.fromJson(jsonReader, token.getType());
} catch (JsonParseException ex) {
throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
}
}
/**
* Write the json reprsentation to {@link OutputStream}.
*
* @param config object to be serialized
* @param output http output message
*/
@Override
protected void writeInternal(
Object config, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
outputMessage.getBody().write(
this.gson.toJson(config).getBytes(DEFAULT_CHARSET.displayName()));
}
}
UPDATE: Added the service layer
public class ConfigPropertyService implements IConfigPropertyService {
private final Log LOG = LogFactory.getLog(ConfigPropertyService.class);
private final IConfigPropertyDao<Config> configPropertyDao;
/**
* Returns the instance of Dao.
*/
@Override
public IConfigPropertyDao<Config> getConfigPropertyDao() {
return this.configPropertyDao;
}
/**
* Initializes the data access tier.
*/
public ConfigPropertyService(IConfigPropertyDao<Config> configPropertyDao) {
this.configPropertyDao = Objects.requireNonNull(configPropertyDao);
}
/**
* {@inheritDoc}
* @throws ConfigServiceException if the resources can't be cleaned up successfully
*/
@Override
public void upsertConfigurationProperties(
ImmutableList<Config> configModels) {
for (Config config: configModels) {
// This for loop throws an exception.
}
// Step # 2: Updating the properties.
this.configPropertyDao.upsertProperties(configModels);
}
/**
* {@inheritDoc}
*/
@Override
public ImmutableList<ConfigModel> readConfigProperties() {
return this.configPropertyDao.readProperties();
}
}
My request body is given as follows. It is a String body with Content-Type: application/json; charset=UTF-8
[{"propertyName":"anchorIndexingFilter.deduplicate","propertyValue":"false","propertyDescription":"With this enabled the indexer will case-insensitive deduplicate hanchors\n before indexing. This prevents possible hundreds or thousands of identical anchors for\n a given page to be indexed but will affect the search scoring (i.e. tf=1.0f).\n ","editable":true}]