Ambiguous mapping when using RequestBody annotation - java

I am trying to create an endpoint in Spring Boot that accepts both single object or an array of these objects. I know that mappings need to have unique signiture so I am wondering what is the correct way to make it work using POJOs?
#RequestMapping(method = { RequestMethod.POST })
public ResponseEntity<String> postSingleFoo(HttpServletRequest request,
#RequestBody(required = true) Foo foo) {
// process
}
#RequestMapping(method = { RequestMethod.POST })
public ResponseEntity<String> postMultiFoo(HttpServletRequest request,
#RequestBody(required = true) Foo[] foo) {
// process
}
Obviously I am getting an exception for ambiguous mapping. But I would still like to use POJOs in my #RequestBody annotation since i am performing couple of conversions in them.
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'requestMappingHandlerMapping' defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'fooController' method
public void com.usquared.icecream.lrs.controller.FooController.postSingleFoo(javax.servlet.http.HttpServletRequest,java.lang.String)
to {[/foo],methods=[POST]}: There is already 'fooController' bean method
What is the recommended approach to implement such feature correctly?

This is not a problem that can be fixed through Spring MVC. Spring MVC creates mappings from the #RequestMapping annotating your handler methods. These help distinguish how Spring MVC delegates HTTP requests to be handled by your methods. Your current configuration attempts to map two handler methods to the same request details. That can never work.
One solution, assuming you're expecting JSON and working with Jackson, is to configure your ObjectMapper to accept single values as arrays and define a single handler method with an array parameter. For example, you'd keep only this handler method
#RequestMapping(method = { RequestMethod.POST })
public ResponseEntity<String> postMultiFoo(HttpServletRequest request,
#RequestBody(required = true) Foo[] foo) {
// process
}
but configure your ObjectMapper as such
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
The configuration depends on how your application is configured. With Spring Boot, it should be as simple as declaring a #Bean method for ObjectMapper. With your typical Spring MVC application, you'll need to register a MappingJackson2HttpMessageConverter with a custom ObjectMapper.
If your JSON, the request body, contained
{
"someProperty":"whatever"
}
Jackson would be able to wrap the single value into a Foo[] and Spring MVC would pass that as an argument to your handler method. You can then check the length of the array and act accordingly.

Related

Spring boot #RequestBody default POJO mapping behavior?

I have a java class with uppercase field names and some of them with under scroll, like this:
public class DATADto {
private String UPPERCASE;
private String UNDER_SCROLL;
public String getUPPERCASE() { return UPPERCASE; }
public void setUPPERCASE(String s) { UPPERCASE = s; }
...//setters and getters
}
and I used this in a rest endpoint that accepts json in a spring rest controller:
#RestController
#RequestMapping({"/api/path"})
public class MyRestController {
#PostMapping(path = {"/Data"}, consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> useDATADto(#RequestBody DATADto aDATADto ) {
//do something
}
}
what JSON fields do I need to send by default and why?
The story goes like this..
Spring Boot by default uses Jackson ObjectMapper to serialize and deserialize Java objects.
In this context, by serialization we mean the conversion of java objects into json, deserialization is the reverse process.
Regarding the #RequestBody annotation, the following is written in the documentation:
Annotation indicating a method parameter should be bound to the body
of the web request. The body of the request is passed through an
HttpMessageConverter to resolve the method argument depending on the
content type of the request. Optionally, automatic validation can be
applied by annotating the argument with #Valid.
In short, #RequestBody annotation tells Spring to deserialize an incoming request body into an object passed as a parameter to the handler method. Spring achieves this using MessageConverter
Since Spring Boot uses Jackson by default for serializing and deserializing request and response objects in your REST APIs, and Jackson uses MappingJackson2HttpMessageConverter, so that will be message converter implementation that spring will use. You can read more about that here.
The important thing is that Jackson uses Java Bean naming conventions to figure out the json properties in a Java class. Acutally it uses default PropertyNamingStrategy . Here is what is written in documentation:
In absence of a registered custom strategy, default Java property
naming strategy is used, which leaves field names as is, and removes
set/get/is prefix from methods (as well as lower-cases initial
sequence of capitalized characters).
So, since you didn't set any naming strategy, it will use default one.
Beacause of that, if you send payload like this :
{
"uppercase": "YOUR_VALUE",
"under_scroll": "YOUR_VALUE"
}
That won't work, you will get exception, since there jackson won't find under_scroll property in your class, it will look for under_SCROLL , therefore this payload:
{
"uppercase": "YOUR_VALUE",
"under_SCROLL": "YOUR_VALUE"
}
will work.
To change default PropertyNamingStrategy check
this article.
It will depend on the Jackson property naming strategy. The default is LOWER_CAMEL_CASE , so your request body should look like this:
{
"uppercase": "test",
"under_scroll": "test"
}
For all possible configurations of the naming strategy for Jackson please refer to the document «Class PropertyNamingStrategy»
If you're using Spring, you may use this property to configure the naming strategy:
spring.jackson.property-naming-strategy
Another possible way will be the bean configuration:
#Bean
public Jackson2ObjectMapperBuilder jacksonBuilder() {
Jackson2ObjectMapperBuilder jacksonMapper = new Jackson2ObjectMapperBuilder();
jacksonMapper.propertyNamingStrategy(PropertyNamingStrategy.LOWER_CASE);
return jacksonMapper;
}
Additional note:
Your current naming approach doesn't follow the Java Code Conventions. If you need to process JSON with some specific naming format better to use the #JsonProperty annotation on the fields of your POJO.
Please see the example below:
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
#Data
public class DATADto {
#JsonProperty("UPPERCASE")
private String uppercase;
#JsonProperty("UNDER_SCROLL")
private String underScroll;
}
You should send post request to /api/path/data with this request body:
{
"uppercase": "YOUR_VALUE",
"under_scroll": "YOUR_VALUE"
}

How can I specify method with an parameterized annotation and its value with #Pointcut

Background:
I am developing an web application with Spring MVC.
I want to make an aspect that is executed on POST requests and not executed on GET requests, because I want to inject the logic that prevent POST requests which are sent before completion of HTML rendering.
#RequestMapping(value = "/aaa", method = RequestMethod.POST)
public String methodForPost(AnDto dto, Model model) {
// the aspect should be executed on this method
}
#RequestMapping(value = "/bbb", method = RequestMethod.GET)
public String methodForGET(AnDto dto, Model model) {
// the aspect shouldn't be executed on this method
}
Question:
How can I specify method with an parameterized annotation and its value with #Pointcut ?
How can I specify method with an parameterized annotation and its value in <aop:pointcut> in Spring applicationContext.xml?
#Around(value="#annotation(RequestMapping)")
public Object display(ProceedingJoinPoint joinPoint, RequestMapping requestMapping ) throws Throwable {
// You have access to requestMapping. From which you can get whether its get or post and decide to proceed or not.
}
More info http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html#aop-ataspectj-advice-params-passing
You might have to extend this to intercept only for Requestmapping's in your package. Because this intercepts every RequestMappig you might have in your project including one used by the libraries which you might be using, which is a burden.

Automatic conversion of JSON form parameter in Spring MVC 4.0

I am trying to build a Spring MVC controller which will receive a POSTed form with a parameter in JSON format, and have Spring automatically convert it to a Java object.
Request content type is application/x-www-form-urlencoded
The name of the parameter that contains a JSON string is data.json
This is the controller:
#Controller
public class MyController {
#RequestMapping(value = "/formHandler", method = RequestMethod.POST)
public #ResponseBody String handleSubscription(
#RequestParam("data.json") MyMessage msg) {
logger.debug("id: " + msg.getId());
return "OK";
}
}
And this is what the MyMessage object looks like:
public class MyMessage {
private String id;
// Getter/setter omitted for brevity
}
Perhaps not surprisingly, posting a form with parameter data.json={"id":"Hello"} results in HTTP error 500 with this exception:
org.springframework.beans.ConversionNotSupportedException:
Failed to convert value of type 'java.lang.String' to required type 'MyMessage'
nested exception is java.lang.IllegalStateException:
Cannot convert value of type [java.lang.String] to required type [MyMessage]: no matching editors or conversion strategy found
If I read the MappingJackson2HttpMessageConverter docs correctly, Jackson JSON conversion is triggered by Content-Type application/json, which I obviously cannot use since this is a form POST (and I don't control the POSTing part).
Is it possible to get Spring to convert the JSON string into an instance of MyMessage, or should I just give up, read it as a String and perform the conversion myself?
Spring invokes your #RequestMapping methods with reflection. To resolve each argument it's going to pass to the invocation, it uses implementations of HandlerMethodArgumentResolver. For #RequestParam annotated parameters, it uses RequestParamMethodArgumentResolver. This implementation binds a request parameter to a single object, typically a String or some Number type.
However, your use case is a little more rare. You rarely receive json as a request parameter, which is why I think you should re-think your design, but if you have no other choice, you need to register a custom PropertyEditor that will take care of converting the request parameter's json value into your custom type.
Registration is simple in an #InitBinder annotated method in your #Controller class
#InitBinder
public void initBinder(WebDataBinder dataBinder) {
dataBinder.registerCustomEditor(MyMessage.class, new PropertyEditorSupport() {
Object value;
#Override
public Object getValue() {
return value;
}
#Override
public void setAsText(String text) throws IllegalArgumentException {
value = new Gson().fromJson((String) text, MyMessage.class);
}
});
}
In this particular case, we don't need all the methods of the PropertyEditor interface, so we can use PropertyEditorSupport which is a helpful default implementation of PropertyEditor. We simply implement the two methods we care about using whichever flavor of JSON parser we want. I used Gson because it was available.
When Spring sees that it has a request parameter that you requested, it will check the parameter type, find the type MyMessage and look for a registered PropertyEditor for that type. It will find it because we registered it and it it will then use it to convert the value.
You might need to implement other methods of PropertyEditor depending on what you do next.
My recommendation is to never send JSON as a request parameter. Set your request content type to application/json and send the json as the body of the request. Then use #RequestBody to parse it.
You can also use #RequestPart like this:
#RequestMapping(value = "/issues", method = RequestMethod.POST, headers = "Content-Type=multipart/form-data")
public String uploadIssue(#RequestParam("image") MultipartFile file, #RequestPart("issue") MyMessage issue)

In Spring MVC, how can I map nested URLs such as /settings/, /settings/users/, and /settings/users/delete?

In Spring 3 MVC, I have a controller that I call SettingsController, and it has methods such as displayUsers() for displaying a list of users, saveUser(), and deleteUser(). SettingsContoller also controls roles and other things.
I'd love to be able to use URL routing such that /settings/users would call displayUsers(), /settings/users/save would call saveUser(), and /settings/users/delete would call deleteUser().
My code is below, and I'm getting the error message that follows the code. What am I doing wrong? Thanks!
#Controller
#RequestMapping("/settings")
public class SettingsController {
#Transactional
#RequestMapping(value = {"/users/save"}, method = {RequestMethod.POST})
public ModelAndView saveUser(details removed){
//details removed
}
#RequestMapping(value = {"/users/delete"}, method = {RequestMethod.POST})
public ModelAndView deleteUser(details removed){
//details removed
}
#RequestMapping(value = {"/users"}, method = RequestMethod.GET)
public ModelAndView settingsUsers(details removed){
//details removed
}
}
Error:
HTTP ERROR: 500
Could not resolve view with name 'settings/users/delete' in servlet with name 'spring'
RequestURI=/das-portal/srv/settings/users/delete
Caused by:
javax.servlet.ServletException: Could not resolve view with name 'settings/users/delete' in servlet with name 'spring'
at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1029)
...
It looks to me like you've set up your controller correctly. As you pointed out, the problem might be in how Spring parses annotations upon start up.
How did you configure Sprint to parse annotations such as #Controller? Do you explicitly set up any sort of HandlerMapping? If you use <context:component-scan>, then it registers a DefaultAnnotationHandlerMapping for you.
The good news is that you can chain multiple handler mapping classes together. The DispatcherServlet will check each one in the order that you specify via the order property of the handler mapping beans (in other words, use the order property to indicate the precedence of your handlers).
So, throw <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/> into your configuration and set its order property as appropriate.
What about using just one method checking mode?
#RequestMapping(value = "/users/{action}", method = RequestMethod.POST)
public String userAction(#PathVariable String action, ...) {
if (mode.equals("save")) {
//your save code here
}
}

How to implement Spring's 3.1 MvcAnnotationDriven?

This is the code example from Spring 3.1 Spring Source Blog: From XML to #Configuration I'm trying to implement in my application (which was done in Spring 2.0 not by me so it's lot of learning).
#FeatureConfiguration
class MvcFeatures {
#Feature
public MvcAnnotationDriven annotationDriven(ConversionService conversionService) {
return new MvcAnnotationDriven().conversionService(conversionService)
.argumentResolvers(new CustomArgumentResolver());
}
// ...
}
However, I can't understand the point of .argumentResolvers(new CustomArgumentResolver()) and their CustomArgumentResolver looks like bellow. What's the point of it?
public class CustomArgumentResolver implements WebArgumentResolver {
#Override
public Object resolveArgument(MethodParameter param, NativeWebRequest request) throws Exception {
RequestAttribute attr = param.getParameterAnnotation(RequestAttribute.class);
if (attr != null) {
return request.getAttribute(attr.value(), WebRequest.SCOPE_REQUEST);
} else {
return WebArgumentResolver.UNRESOLVED;
}
}
}
To add to #GaryF's answer, and to clarify some points, Spring 2.5 introduced annotated controllers, which replaced the old interface-style controllers of Spring 2.0. These new controllers have methods with no fixed parameters - the method declares the parameters that it needs to do its job, and nothing more.
For example, say a controller method needed one thing to do its job - a request parameter that contains the ID of an object from the database. In Spring 2.0, you would need to implement something like AbstractController.handleRequestInternal(), e.g
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) {
String id = request.getParameter("id");
MyThing obj = getObjById(id);
//... do stuff and return ModelAndView
}
Spring 2.5 made that easier:
#RequestMapping
public ModelAndView handle(String id) {
MyThing obj = getObjById(id);
//... do stuff and return ModelAndView
}
Here, we only declare parameters for the stuff we need.
So far so good, but this is where a custom WebArgumentResolver comes in. Say I want to remove the getObjById from my controller altogether, because maybe I think it clutters up the code, and maybe it's used across many other controller methods. Instead, I want to do this:
#RequestMapping
public ModelAndView handle(MyThing id) {
//... do stuff and return ModelAndView
}
It's even simpler, and has a bare minimum of boilerplate code. A custom WebArgumentResolver can be registered with the app-context which recognises parameters of type MyThing, and knows how to extract the information from the request. Spring invokes that resolver, and passes the result to the controller method.
Custom resolvers aren't commonly used, but can be very handy in the right situation.
The example in your question uses CustomArgumentResolver to resolve the example's custom RequestAttribute class. The resolver pulls out request attributes and binds them to RequestAttribute objects, so that they can be declared as controller method parameters.
WebArgumentResolvers are a way for you to specify how the parameters of MVC-mapped methods should be resolved. If you'd like to use a custom object as a parameter for an MVC-mapped method, Spring tries to figure out how make sense of it in it's own way. Typically this would happen through binding, where some http parameters you submit match up with the fields of the object and Spring matches them up and creates a new object for you.
If you ever have a situation where the submitted parameters don't match up quite so neatly with your method parameters, WebArgumentResolvers are there to fill in the gap: you provide custom logic so Spring doesn't have to figure it out.
In your example, param is one such parameter to be matched up. This piece of custom code first checks if the parameter has an #RequestAttribute annotation. If it does, then the custom code pulls the value from that object and looks it up as an attribute on the http request, returning it. It it does not have that annotation, then the method returns the UNRESOLVED value, which simply indicates that this WebArgumentResolver doesn't know anything about this particular parameter and Spring should try a different method (such as binding).

Categories

Resources