Using #RequestBody and #ModelAttribute together? - java

I'm trying to get at the body of a POST, and I'd like the parameters of my method to bind to an object.
Is this possible?
My current declaration doesn't ever get hit:
#RequestMapping(method = RequestMethod.POST)
public void doStuff(#RequestBody byte[] bodyData, #ModelAttribute Form form, Model model ) {
Looks like I'm getting this exception:
- 2011-02-25 16:57:30,354 - ERROR - http-8080-3 - org.springframework.web.portle
t.DispatcherPortlet - Could not complete request
java.lang.UnsupportedOperationException: #RequestBody not supported

For this to work correctly, you have to be sure you're using AnnotationMethodHandlerAdapter. This overrides HandlerMethodInvoker's createHttpInputMessage (which is throwing the exception you're seeing). (It does this in a private class.)
I believe you can just include the following in your *-servlet.xml
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>
WARNING: The below answer is for the case of needing #RequestBody and #RequestParam in the same handler method. It does not answer this question, but could be of use to someone.
I've tested this out using Spring 3.0.1. This is possible, but it's somewhat precarious. You MUST have your #RequestBody method argument before your #RequestParam argument. I'm guessing this is because HandlerMethodInvoker reads the request body (along with the GET parameters) when retrieving parameters (and the request body can only be read once).
Here's an example (WARNING: I code in Scala, so I've not compiled this Java code)
#RequestMapping(value = "/test", method = RequestMethod.POST)
public String test(
#RequestBody String body,
#RequestParam("param1") String parma1,
Map<Object, Object> model: Map[AnyRef, AnyRef])
{
model.put("test", test)
model.put("body", body)
return "Layout"
}
An alternative is to use #PathVariable. I've confirmed that this works.

Unfortunately that is kind of impossible. If you are using portlet version of Spring MVC (and it looks like from the logs) then you might be interested in this JIRA issue.
AnnotationMethodHandlerAdapter uses PortletHandlerMethodInvoker internally and the second is a inner subclass of HandlerMethodInvoker - the place where you can configure HttpMessageConverter-s. But they're set to null. And the property is final.
That even would be to workaround if you could substitute HandlerMethodInvoker, but you can not.. it's constructor-created ;)
One thing to notice is that Servlet version of Spring MVC fully supports HttpMessageConverter-s and does not suffer this issue.

Related

Spring MVC #ModelAttribute as a method parameter annotation

I bumped into an answer about the usage of #ModelAttribute on spring MVC here on stackoverflow, and learned that it was not actually required to be added into the method's parameter.
I looked for some controllers from our old project, deleted the annotation, and surprisingly the application still runs flawlessly without the #ModelAttribute. Please see example below:
#RequestMapping(method = RequestMethod.POST, value = "/audit/filter")
public String getAuditLogsWithFilter(Model model, AuditLogFilter auditLogFilter, BindingResult bindingResult)
I have read some articles about it but I can not grasp onto why #ModelAttribute is used for some method parameters particularly for spring controllers.
Can anybody provide a simple explanation into why that is? Or can someone enumerate some cases into which I should add the #ModelAttribute annotation to my parameter object?
as described in the official document, it's optional:
Note that using #ModelAttribute is optional (for example, to set its attributes). By default, any argument that is not a simple value type (as determined by BeanUtils#isSimpleProperty) and is not resolved by any other argument resolver is treated as if it were annotated with #ModelAttribute.

Missing Request header "Accept" with Spring 5.x

I was using Spring version 4.x, and with the upgrade to 5.x - I have noticed that some of the API requests are failing (MissingRequestHeaderException) if the Accept header is not provided in the request.
The interface doesn't really need them, as it is not being used. The API's without it in the interface works fine. A solution would be to remove it from all the API's wherever it's there. But that's not a path we want to take now, I am looking for a general solution that could be applied to all API's without having to change each one separately.
Is there a way I could ask Spring to ignore this parameter in the interface ?
Or maybe handle the MissingRequestHeaderException so as to ignore it and process the API request, is that possible?
The API Interface:
public Void setEmployeeDetails( #PathVariable( "employeeId" )Integer employeeId, #Valid #RequestBody EDetails eDetails, String accept )
The API Controller implementing the interface:
Void setEmployeeDetails( #ApiParam(value = "ID of the employee.",required=true ) #PathVariable("employeeId") Integer employeeId,
#ApiParam(value = "" ,required=true ) #Valid #RequestBody EDetails eDetails,
#RequestHeader("Accept") String accept)
Javadoc of MissingRequestHeaderException says:
ServletRequestBindingException subclass that indicates that a request header expected in the method parameters of an #RequestMapping method is not present.
Checking the Spring source code, it also seems like that exception is only thrown by RequestHeaderMethodArgumentResolver, which says:
Resolves method arguments annotated with #RequestHeader except for Map arguments. See RequestHeaderMapMethodArgumentResolver for details on Map arguments annotated with #RequestHeader.
An #RequestHeader is a named value resolved from a request header. It has a required flag and a default value to fall back on when the request header does not exist.
So it would seem that if you're getting that error, it's because your code is asking for the Accept header and did not specify required=false, e.g.
#RequestMapping(...)
public void foo(#RequestHeader(name="Accept", required=false) String accept) {
// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑
// Missing!
...
}

Is there a way to clear a "consumes = MediaType" in Spring Boot RestController?

Let's say I have a controller that has a variety of endpoints (GET/POST/PUT/DELETE) and generally they both produce and consume JSON, so I do:
#RestController
#RequestMapping(value=["/some/base/path"], produces = [MediaType.APPLICATION_JSON_UTF8_VALUE], consumes = [MediaType.APPLICATION_JSON_UTF8_VALUE])
public class SomeController {
...
}
But it turns out that my #GetMapping does not consume JSON (and I don't want to force callers to set Content-Type: application/json for GET requests. Is there a way, on the #GetMapping, to clear/empty the consumes = value that was set at the class level? Or is there another way to avoid repeating the consumes attribute on all methods in the class?
I've already tried setting the #GetMapping(value=["/some/path"], consumes = []) without any luck. For context, I'm converting from Jersey annotations to Spring REST controller style annotations and I'm finding this to be an annoying difference in behavior (setting a class-level #Consumes annotation doesn't get enforced against #GETs). And just looking for an elegant way to mirror existing behavior without cloning the consumes attribute all over the place.
I got your problem now .Try use the below solution and refer to this link that might help
https://github.com/spring-projects/spring-framework/pull/1257/commits/00e6ca412dffeb8a7a596f9312db19eb6cc49525
#GetMapping(value = "/get", consumes = MediaType.ALL_VALUE)
For your case , you need to delete the consumes part.I mean just use the Produces only.For example :
#GET
#Produces("application/json")
#Path("/{oid}")
public Book getBook(#PathParam("oid") String oid) {
return bookService.getBook(oid);
}
or check this url :
https://dzone.com/articles/spring-boot-building-restful-web-services-with-jersey

Why is my Spring service returning any content type requested by client?

I have a Spring rest service using Spring 3.1.0.RELEASE. Here is the relevant code for the service call in question:
#RequestMapping(value="/{var1}", method=RequestMethod.GET, produces="application/json")
#ResponseBody
public String getSomeStuff(#PathVariable final String var1) {
return myJsonString;
}
If I call this using the following curl command, it happily returns me my json string with a content-type of application/xml whereas I would expect a 406 based on the Spring 3.1 docs:
curl -v -H "Accept: application/xml" http://localhost:8080/MyServiceSite/myvalue
There is no extra configuration in my app for this service (no serialization), I am returning raw json with no post-processing for the service configured. I'm certain I have missed something, can anyone point out anything I may have missed?
Edit: Here is the documentation I was looking at when attempting to get this working. Specifically section 16.3.2.5. My code is very similar except that their code looks like it assumes config setup to let Spring handle serialization. Perhaps the produces does not work when bypassing the Spring serialization?
Edit: I changed my expectation for the response code. A 415 would indicate I was sending improper content in my request body whereas 406 is proper for having an accept header that doesn't jive with the content type of the server.
Anyway, I have changed this method do return a Map and added config for it to serialize to json and now if I send an invalid content type from the client I get the proper 406 response. It seems that maybe the "produces" setting is ignored when the output of the method is not being serialized.
The produces condition is new to Spring MVC 3.1 and is only supported with the RequestMappingHandlerMapping and related #MVC support classes, also new in Spring 3.1. My guess is that you're using the 3.0 #MVC support classes, which do not support the produces condition. Your code otherwise is correct and so are your expectations of what should happen.
The use of headers="Accept=application/json" is unnecessary in 3.1. That's exactly what the produces condition was introduced for.
What about the headers attribute for the #RequestMapping. You could set the Accept header in there. Something like:
#RequestMapping(value="/{var1}", method=RequestMethod.GET, produces="application/json", headers = "Accept=application/json")
#ResponseBody
public String getSomeStuff(#PathVariable final String var1) {
return myJsonString;
}
I don't know how Spring would handle a request to that path without a matching header. If it doesn't give what you want you might need to define a similar mapping without the headers and have it send back a ResponseEntity and set the response code or something, but I would hope it would handle it appropriately.

Spring - Abstracting url mapping to handle common url parameters

Assume the following setup:
We have multiple commands mapped to different URLs, each of these with its own body, which we can capture using mappings, like:
#RequestMapping(value = "url1/{param}/command", method = RequestMethod.POST)
#ResponseBody
public Response command1(#PathVariable("param") String param,
#RequestParam(value = urlParam) Param urlParam,
#RequestBody Request request) {
...}
We have several cases where the same parameter repeats in several urls, specifically the URL parameter. Since we have several such variables, today we manually add them to each mapping which is error prone and too verbose.
Is there anyway of routing all mappings through an initial mapping, capturing all those url parameters, and thus remove the clutter from all other mappings?
If you switch from Spring MVC to any JAX-RS framework (e.g. Jersey, Apache Wink), you can use subresources:
#Path("/parent/{id}")
class ParentResource {
#Path("/child1")
Child1Resource getChild() {
....
}
#Path("/child2")
Child2Resource getChild() {
....
}
}
Pay attention that methods with #Path annotations are not annotated with HTTP Methods, so any relevant HTTP request matching the url will propagate into the subresources.
Another suggestion to reduce the error-proning: use constants (public final static String) as parameters both when you create the url and when you use the parameter. This makes it a little bit more verbose, but reduce the error-proning. It can be used both with Spring-MVC and JAX-RS. Don't forget that it's possible to put constants inside the annotation values.
Hope this helps.

Categories