I am working on spring REST APIs. In requirements, there are 2 POST requests with same URL but different request body. Since Spring MVC must have unique mappings across controller, I have to pre-process the request body to map to a specific POJO.
On the basis of session_type in request body, I have to map the request to specific POJO (JSON -> JAVA POJO).
For example, if 'session_type' in request body is 'typeX' then the request should map to ClassX POJO. If 'session_type' in request body is 'typeY' then the request should map to ClassY POJO.
If there a way to do it using some kind of requestbody annotation?
If you want to bind typeX and typeY, then you definitely need 2 handlers. But, why wouldn't we use param option of #RequestMapping:
#RequestMapping(method = RequestMethod.POST,
value = "/url", params = "session_type=typeX")
public String handleTypeX(#RequestBody #ModelAttribute TypeX typeX){
//TODO implement
}
#RequestMapping(method = RequestMethod.POST,
value = "/url", params = "session_type=typeY")
public String handleTypeY(#RequestBody #ModelAttribute TypeY typeY){
//TODO implement
}
If you need some preparations (f.e. normalize params or perform model binding manually), then the approach above you may combine along with #InitBinder, but please note, that #InitBinder needs exact ULR's rules along with #ModelAttribute parameters in handlers.
EDIT: In Spring MVC there is no possibility to use 2 handlers for exact URL, i.e. when method/URL/params/consumes type are the same.
Thus I suggest use unified handler, where you would check necessary parameter and then manually convert into corresponding class. For finding necessary class I suppose it would be better to use Strategy pattern:
//class resolver according "session_type" parameter
//note, that you can use Spring autowiring capabilities
private final Map<String, Class> TYPES_CONTEXT = new HashMap<String, Class>(){
{
this.put("x", TypeX.class);
this.put("y", TypeY.class);
//TODO probably other classes
}
}
#RequestMapping(method = RequestMethod.POST,
value = "/url")
public #ResponseBody String handleAnyType(#RequestBody Map<String, String> body){
String sessionType = body.get("session_type");
//TODO handle case if sessionType is NULL
Class convertedClass = TYPES_CONTEXT.get(sessionType);
//TODO handle case if class is not found
Object actualObject = objectMapper.convertValue(body, convertedClass);
//now we use reflection for actual handlers, but you may refactor this in the way you want, f.e. again with Strategy pattern
//note that current approach there should be contract for methods names
Method actualHandler = this.getClass().getMethod("handle" + actualObject.getClass().getSimpleName());
return (String)actualHandler.invoke(this, actualObject);
}
public String handleTypeX(TypeX typeX){
//TODO implement
}
public String handleTypeY(TypeY typeY){
//TODO implement
}
//TODO probably other methods
This approach doesn't handle validation and some things were omitted, but I believe this might be helpful.
I think you should created controller with one method for both types, and call required component\method in it depending on typeX or typeY.
GETs shouldn't have request bodies, or at least if they do, the server side isn't required to do anything with them. As you describe it, this API isn't RESTful.
Assuming you don't care about that, try creating a controller method that takes a parent class of TypeX and TypeY, or interface that both TypeX and TypeY implement, annotate it with #SomethingMeaningfulToYou, then use a web argument method resolver to instantiate the child class you want.
It's a hack around a broken API though.
there are 2 POST requests with same URL but different request body
For a RESTful interface, the same URL should always indicate the same resource. The body of a request may contain different representations of that resource. You could create different HttpMessageContverter classes for the two different kinds of representation.
Related
I have two endpoints in a controller mapped to the same path (the controller's root path) with different MIME types.
#RequestMapping(method = RequestMethod.GET, produces = {"application/a+json"})
public ResponseEntity<URI> methodA() {
}
#RequestMapping(method = RequestMethod.GET, produces = {"application/b+json"})
public ResponseEntity<URI> methodB() {
}
When no Accept header is sent with the request, the response is always of type application/a+json.
How is spring-mvc choosing that by default? After some trials, my observation is that it's being chosen based on the alphabetical order (MIME type starting with a vs starting with b), but I didn't find any documentation around that. Is that how it works?
Even if you are seeing some order, I would advise to not rely on it.
Instead define another method that does not declare producesand in this way you will know for sure that Accept was not sent - instead of :
was in sent with application/a+json or not sent at all.
This 3-rd method could do nothing really, it could just delegate to whatever you already have, initially logging the request for example.
I have a Spring REST application that accepts JSON messages, written like
#RequestMapping(value = "/myhook", method = RequestMethod.POST,
produces = JSON, consumes = JSON)
public #ResponseBody MyResponse doIt
(#Valid #RequestBody(required = true) MyContractRequest request) {
MyResponse response;
...
return response;
}
This works really well with almost no code to support, but now I have a requirement to sign both response and request.
I started from simply computing the shared signature of all message fields at Java level and assigning it to the dedicated signature field. However this requires to have and maintain code for computing the signatures:
public void update(java.security.Signature sign) throws Exception {
sign.update(name);
sign.update(value);
sign.update(etc);
}
Some people around me expressed opinion that the need to write and maintain this signing code may not be the best design, and it may be better to sign the whole message as a single JSON string. I could fetch the request as a string manually, and then process JSON manually, but I really would like to preserve the Spring controller concepts.
Also, I cannot longer have the signature field in the message itself because the value of this field obviously also changes the signature of the JSON string.
Is there any way to compute the signature of the whole JSON message body on the message departure and arrival, and where to place the signature so it could be passed together with the message? One of the idea is to use the custom HTTP header for the signature. Anyway, how to compute it first?
You can use a servlet filter with Spring MVC and modified your content whatever you want in request and response as well
Example :
http://www.mkyong.com/spring-mvc/how-to-register-a-servlet-filter-in-spring-mvc/
or you can use Spring 3 MVC Interceptor
http://viralpatel.net/blogs/spring-mvc-interceptor-example/
In a SpringBoot REST application I have a TableRequest type that contains column sorting, filtering, and paging details for GET requests for tabular data. It's generic in that it doesn't care what the specific data being requested is, it only specifies generic table parameters. As such it's applicable across many different controller methods. Also, because it applies to GET requests the fields are passed as request parameters (no #RequestBody json parameter). I've got a #ModelAttribute method inside the controller class that parses the request parameters into a TableRequest object, then the actual #RequestMapping method receives that object as a #ModelAttribute parameter.
Because the TableRequest class is generic, I'd like to be able to use it across multiple controllers without having to copy the parsing logic into every one. I'm wondering if there's a Spring-y annotation-based way of reusing the same #ModelAttribute method any time a controller has a TableRequest input parameter.
Thanks in advance :)
My Solution (based on selected answer below)
I created a #TableRequestController annotation and a corresponding #ControllerAdvice class that applies only to controller classes that have that annotation. That ControllerAdvice class includes the #ModelAttribute method tht parses the GET request parameters into a TableRequest object.
The one important caveat here is that the new #TableRequestController may only be applied to Controller class as a whole, not to individual controller methods. As such, I created a separate inner controller class, tagged with that annotation, whose #RequestMapping methods all accept a TableRequest object.
#TableRequestController:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface TableRequestController {}
ControllerAdvice class:
#ControllerAdvice(annotations = TableRequestController.class)
public class TableRequestControllerAdvice {
#ModelAttribute
public TableRequest tableRequest(
#RequestParam Map<String, String> params,
#RequestParam int pageStart,
#RequestParam int pageSize) {
return new TableRequest(params, pageStart, pageSize);
}
}
TableRequest REST controller class:
#RestController
#TableRequestController
public static class MyTableRequestController {
#RequestMapping("/the/table/request/url")
public MyResponse makeTableRequest(
TableRequest tableRequest) {
return new MyResponse(tableRequest);
}
}
You can use #ControllerAdvice. Everything defined here applies to all controllers, or a defined subset, if you prefer that.
Documentation
Another alternative (better imho) is to write a message converter. It only handles one specific type. You no longer need #ModelAttribute but simply have a TableRequest parameter in you controller method.
In a SpringBoot REST application I have a TableRequest type that
contains column sorting, filtering, and paging details for GET
requests for tabular data. It's generic in that it doesn't care what the specific data being requested is, it only specifies generic table parameters
This means what you have is a utility service.
So define the class as service and access it inside the views.
Access any beans in your application context using SpringEL’s syntax: ${#myBean.doSomething()}
I have a scenario to pass values from one request to another subsequent rquest. (i.e) I will call 'Controller1' on the first request and take the request parameters or query string and should send them to 'Controller2' as 'new request'.
Strictly I should not use any of the following approaches.
should not use sessions.
should not use cookies.
should not use requestdispatcher.forward(--).
without FlashAttributes (which internally uses session, which won't work in 'Clustered environmnets').
should not expose the ModelAttribues in request parameters in case of redirection (i.e) I should not even expose them as request parameters using spring RedirectView.
please let me know, if we have any alternative approch.
Thanks in advance.
You could call the underlying method directly
So if you have as controller2 :
#RequestMapping(value = "/MyURL", method = RequestMethod.POST)
public String myMethod(final BaseDTO baseDTO, Model model) {}
Inject controller2 into controller1 and call "normally":
controller2.myMethod(baseDTO, model);
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.