Distinguish Spring Boot PostMapping based on key in RequestBody - java

I have a REST endpoint with a PostMapping that should be able to accept different objects in the body and map them based on existence of keys.
When I use the same PostMapping for both functions, it gives me an "Ambiguous mapping" error.
When I use params similar to https://www.baeldung.com/spring-requestmapping, the default mapping is called even if the specialKey exists in the request body.
Is there any workaround to achieve this?
#PostMapping(value = "/classes", params = {"specialKey"})
public ResponseEntity<Class> createClass(#Valid #RequestBody SpecialClass class) throws URISyntaxException {
// do something special
}
#PostMapping("/classes")
public ResponseEntity<Class> createClass(#Valid #RequestBody Class class) throws URISyntaxException {
// do something
}

Based on Mapping the same url to different methods based on request body in spring it's not possible (or at least wasn't at the time). The params needs a separate request parameter, it can't be used to look for things inside the request body like that.
You could include the parameter in the URI, the special endpoint would be /classes?specialKey, and the normal endpoint just /classes. But I would just use different paths.

Related

Managing any HTTP request in a generic way

In my organisation, when I want to expose an API, I have to declare it with a swagger contract, same for any update, and it can take multiple weeks before the creation or change is taken into account.
That's why we've come with the idea to declare only one contract for all the APIs we need to expose, and manage the routing in an applicative reverse proxy (the request would include the necessary metadata to allow to route to the appropriate endpoint) :
{
"genericHttpRequest" : base64encodedByteArrayOfAnyHttpRequest
}
Now the question is :
how to manage this request without reimplementing HTTP ? Is it possible to put back the array of byte into a structured HttpServletRequest ?
/**
* Manage a generic request
*/
#RequestMapping(value = "/genericRequest", method = RequestMethod.POST)
public #ResponseBody void manageGenericRequest(#RequestBody GenericHttpRequestDto body) {
byte[] genericHttpRequest = body.getGenericHttpRequest();
//(...)
}
Spring will inject a HttpServletRequest if it is set as a method parameter. Furthermore, wildcard path mappings will enable the methods to be matched to every request:
#RestController
#RequestMapping("/generic-endpoint/**")
public class DemoController {
#RequestMapping
public ResponseEntity<Object> genericGetRequest(HttpServletRequest httpServletRequest) {
return ResponseEntity.ok().body(httpServletRequest.getMethod());
}
}
Optionally, you could return a ResponseEntity to gain more control over your HTTP response.

web request with request params and content body

I've used Matlab's webwrite() to make calls to a REST API, providing the requset params. However, I need to now make a call where the Request Body must be specified. Is there a way to do this?
The REST API is defined by a Java Spring controller, e.g.:
#PostMapping(value = "post")
public ResponseEntity<?> setMySTuff(
#RequestParam(name = "myId") int myId,
#RequestBody Collection<MyCustomObject> myObjList) {
THe data paramemter for webwrite seems intended for being a set of key/value request param pairs, and not a means of setting the request body.
If I remember correctly, #RequestParam is used for mapping values as query parameters, while #RequestBody defines the content of the response. If my assumptions are valid, the Matlab equivalent should be:
url = ['http://mywebsite.net/service/?myId=' num2str(5778)];
body = struct('Item1','Hello','Item2','World');
opts = weboptions('MediaType','application/json','RequestMethod','post');
response = webwrite(url,body,opts);

How does Spring call these #RequestMappings

The source code for Spring OAuth2's AuthorizationEndpoint contains two redundant #RequestMapping annotations for the same /oauth/authorize endpoint. One of them specifies the POST method, while the other does not specify a method.
How are the two #RequestMapping annotations interpreted? Does the one that specifies POST exclusively handle all POST /oauth/authorize requests, and does the one that does not specify a method exclusively handle any non-POST requests to /oauth/authorize? Or do both methods overlap, with both methods being called for certain requests?
This is probably a Spring MVC question, though the source code on GitHub uses Spring MVC to define what is Spring OAuth2.
Though the complete source code is available on GitHub at the link that the top of this OP, the headers for the two relevant methods are summarized here as follows:
#RequestMapping(value = "/oauth/authorize")
public ModelAndView authorize(Map<String, Object> model, #RequestParam Map<String, String> parameters,
SessionStatus sessionStatus, Principal principal) {
//other stuff
}
#RequestMapping(value = "/oauth/authorize", method = RequestMethod.POST, params = OAuth2Utils.USER_OAUTH_APPROVAL)
public View approveOrDeny(#RequestParam Map<String, String> approvalParameters, Map<String, ?> model,
SessionStatus sessionStatus, Principal principal) {
//other stuff
}
This is already explained in the official documentation: if you provide the values for the method field, they'll be used to narrow down the mapping. In other words: Spring MVC will use these hints to find the most precise match for each request.
It's also easy to build a simple proof-of-concept application that demonstrates it in practice:
#RequestMapping("/foo")
#ResponseBody
public String hello() {
return "hello, default";
}
#RequestMapping(value="/foo", method = RequestMethod.GET)
#ResponseBody
public String helloGet() {
return "hello, GET";
}
Hitting /foo with a GET request, for instance using Postman, will return "hello, GET". All other supported HTTP methods (POST, PUT, DELETE, etc.) will result in "hello, default".
The default method used by Spring request mapping is GET, so if you specify a request mapping with only #RequestMapping annotation, Spring will route all GET requests for the value of the annotation to this method.
To use any other method you basically need to say the method in the annotation. like #RequestMapping(method = RequestMethod.POST)
So for your example the first method will only handle the GET requests, while the other will handle the POST requests exclusively.
Usually GET in OAuth is used for normal interpretations, while the POST is used to authenticate un-authenticated users using the param passed to the method, which in this case is OAuth2Utils.USER_OAUTH_APPROVAL.
How are the two #RequestMapping annotations interpreted?
First of, from http://javatechig.com/java/spring/how-spring-controller-request-mapping-works-in-spring-mvc the default is interpreted as a GET. This is the first distinction. Second the paramaters of both methods are slightly different where method 1 requests a Map<String, String> and the other method Map<String, ?>. So even if both methods were GET, it would still make the distinction on parameter level.
Does the one that specifies POST exclusively handle all POST
/oauth/authorize requests, and does the one that does not specify a
method exclusively handle any non-POST requests to /oauth/authorize?
Or do both methods overlap, with both methods being called for certain
requests?
The POST exclusively handles post and nothing else. The other method only handles GET requests. They never overlap. As is java's law and Spring is still bound by the rules of the java overlords =)

Spring MVC using same path on endpoints to return different content?

I'm going to use a very basic hello world endpoint as an example
#RequestMapping("/hello")
public String hello(#RequestParam(value="name", required=false, defaultValue="World") String name, Model model) {
model.addAttribute("name", name);
return "helloworld";
}
If I have this endpoint and I want to be able to go to /hello and retrieve the helloworld view.
Is it possible for me to use the SAME /hello path to retrieve model as json if I pass in a specific request param like content-type?
You could try passing in a parameter using the RequestMapping params option. This does require modifying the URL, but the mapping is still the same and a mapped method without a params tag could be added as a default.
#RequestMapping(value="/hello" params= param1)
public returnType method(#RequestParam("param1") p) { ... }
#RequestMapping(value="/hello" params= param2)
public differentreturnType method2(#RequestParam("param2") p) { ... }
So to handle the first, request URL : http://etc.com/hello?param1=x and the second http://etc.com/hello?param2=y.
Params section of #RequestMapping docs: http://docs.spring.io/spring/docs/4.0.5.RELEASE/javadoc-api/org/springframework/web/bind/annotation/RequestMapping.html#params--
I'm not sure I understand what you mean.
If you mean that you want to be able to send a request to /hello and get two different responses, with different content types, yes, you can do that.
#RequestMapping identifies a method as being a request handler, but it also provides options for restricting when the handler should be used.
In this case, you should use the Accept header in your HTTP request and set it to application/json for a response containing JSON and text/html for a response containing HTML.
You can then have two #RequestMapping methods like
#RequestMapping(value = "/hello", produces = "application/json")
public SomeType handleJson() {...}
#RequestMapping(value = "/hello", produces = "text/html")
public String handleHtml() {...}
Spring will determine which method to use based on the request's Accept header and the method's produces value.

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