Spring RestController url for findById and findByIds - spring

In my Spring Boot application I have a following REST controller:
#RestController
#RequestMapping("/v1.0/decisions")
public class CriterionController {
#Autowired
private CriterionService criterionService;
#RequestMapping(value = "/{decisionId}/criteria/{criterionId}", method = RequestMethod.GET)
public CriterionResponse findById(#PathVariable #NotNull #DecimalMin("0") Long decisionId, #PathVariable #NotNull #DecimalMin("0") Long criterionId) {
Criterion criterion = criterionService.findById(criterionId);
return new CriterionResponse(criterion);
}
}
Everything is working fine and I'm able to retrieve Criterion by its ID.
Right now I need to add additional logic to my CriterionController that will retrieve Criterion by a set of IDs.
Right now I'm in doubt how it can be implemented.. For example should I add a separated endpoint something like:
/{decisionId}/criteria/{criterionIds}
or for example reuse existing one for this purpose or in some other way. Please advise how to implement it according to a best practice of REST.

This is a tricky question, but there are 2 options I can suggest:
/{decisionId}/criteria?id=1&id=2&id=3
or
/{decisionId}/criteria?id=1,2,3
The former could be seen as more RESTful but can end up with a very long URL since you'll be specifying the query parameter each time.
The latter aggregates the ids in a comma separated list. I personally prefer this option and would go for this.
Although not about REST, both URLs are accepted in Section 3.2.8 of RFC 6570

Related

Jersey JAX-RS and OpenaAPI deepObject = true deserialization problem

We have problems deserializing a query parameter with square bracket notation (?paging[offset]=3) in Jersey.
We're using Jersey JAX-RS and annotating our endpoints and beans with swagger OpenAPI, and have tooling to generate our documentation automatically. We want to follow the JSON:API standard for describing a new API. JSON:API specifies that to implement paging, the API must accept a query parameter in the format : ?paging[offset]=0&paging[limit]=10
Our swagger annotations support this out of the box, allowing us to specify
#Parameter(
description = "paging",
style = ParameterStyle.DEEPOBJECT,
explode = Explode.TRUE)
Which is compatible with the square bracket notation paging[offset] and so on. And it generates the correct documentation for our paging parameter. All is good and great and dandy.
JAX-RS is the problem. There's a #QueryParam annotation in JAX-RS. But, to use a complex object with the #QueryParam annotation, that type must have a constructor with a single String parameter. No problem. Let's add a constructor to our paging bean.
public class PagingBean {
public PagingBean(String stringValue){...}
#XmlElement
public getOffset(){...}
public setOffset(int offset){...}
#XmlElement
public getLimit(){...}
public setLimit(int limit){....}
}
So our endpoint now looks like
#Get("/path")
public Response someEndpoint(
#Parameter(description = "paging",style = ParameterStyle.DEEPOBJECT,explode = Explode.TRUE) #QueryParam("paging") PagingBean paging
){
...
}
But if we hit our api with
GET /rest/path?paging[limit]=10&paging[offset]=5
We can see that the paging request parameter is null. It seems like Jersey didn't even recognize that the paging[... is part of the paging QueryParam. Probably that it expects exactly the paging key, and not a paging\[?-like key.
We can confirm this by injecting a #Context UriInfo ui and checking the request parameters. Their key are paging[offset] and paging[limit]
One solution to this is to flatten our parameters in the endpoint like so
#QueryParam("paging[limit]") pagingLimit,
#QueryParam("paging[offset]") pagingOffset
But this is not very nice to look at.
Ideas on how to deserialize this in Jersey ?

Is there a way to map a Retrofit #QueryMap to some object for a Spring Rest Service?

The client interface looks like this
#GET("v3/users/posts")
Call<User> loadPosts(#QueryMap Map<String,String> data);
The RestController should then process the map of query data returning the user's post. There are multiple parameters that can be put in the map as shown in the UserService.findUserPosts(). Is it possible to use a map to pass data to the Spring Rest controller? The restriction I have is this is inherited from code using #Query parameters but it has now grown to quite a number and a query map would limit the changes on the client. I would be really grateful for some feedback. Many thanks
#RestController
public class UsersController{
#RequestMapping(value = "/user/posts", method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<User> getUserPosts(.......What here) {
List<Posts> posts = userService.findPostsBy(id,postKey,offset,when);
}
Solved it. For anyone in the future who wants to do what I am doing , simply provide Request parameters for every key pair in the map
i.e #RequestParam("id") String id,
#RequestParam("postKey") Long, #RequestParam("offset") etc.

Spring REST #RequestBody consume (XML or JSON) to POJO without annotations

I am writing a Springboot REST endpoint and want to consume XML or JSON requests for a simple service. In either case I want Spring to construct an #RequestBody pojo WITHOUT annotating any of the POJO. Is this OK? Safe? Performant?
I was reading this which told me about configuration by exception. To me this means if I structure my request to contain the exact name and case as the POJO member variables I want to populate the #RequestBody will be able to create my class SomeRequest.
If this is my REST endpoint:
#RequestMapping(value = GET_FOR_SOMETHING, method = RequestMethod.POST,
consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE},,
produces = {MediaType.APPLICATION_JSON_VALUE})
public #ResponseBody
StatusResponse<Boolean> getMpdForReqest(#RequestBody SomeRequest request)
And this is my POJO:
public class SomeRequest {
String one;
String two;
public String getOne() {
return one;
}
public void setOne(String one) {
this.one = one;
}
public String getTwo() {
return two;
}
public void setTwo(String two) {
this.two = two;
}
}
My JSON request:
{
"one": "str",
"two": "str"
}
My XML request:
<SomeRequest>
<one>str</one>
<two>str</two>
</SomeRequest>
My question is: why should I not do this or is it perfectly fine?
Thank you all.
TLDR; It is perfectly fine.
Is this OK? Safe? Performant?
Yes, it is as performant as it's annotated cousin, if you take program efficiency into account.
If you take the Programmer efficiency into account, it is much more efficient as the developer doesn't have to deal with a bunch of annotations.
Speaking of Programmer efficiency, I would encourage you to use project Lombok instead of crapping your POJO with bunch of getter and setter methods, that's what cool kids do now a days.
Catch
This will work fine as long as your json fields are one word and small case.
When you have multi-word field name, Java standard is the camelCase and usually JSON standard is the snake_case. In this case, you can either have a Class level Annotation (one per class, so not much ugly). Or, since you are using spring boot, you can use an application wide property (spring.jackson.property-naming-strategy = SNAKE_CASE ).
If you have weird json field names with spaces in between, you might need to use #JsonProperty annotation. Remember, this is a perfectly valid json
{
"just a name with a space" : "123"
}
POJO as RequestBody works perfectly fine. Just note that Spring however will return 400 - Bad Request for every request that can not be mapped to the #RequestBody annoted object.

How to auto generate response fields that do not have POJO

We have a service that simply returns the json document on a GET request. Since we do not have the POJO for the response "model", it appears we won't be able to use the auto response fields generation "goodness".
One option for us is to create the Pojos (quite large, about 50 attributes) and a corresponding controller that uses the pojos. This is awkward as we now have to maintain the model and corresponding controller just so we can auto generate the model.
Any ideas on how we can still leverage some auto generation of the response fields would be greatly appreciated.
Here's the controller I'm referring to:
#RestController
#RequestMapping("/api")
public class ProductController {
#Autowired
ProductService productService;
#RequestMapping(value = { "/products/{ids}" }, method = { RequestMethod.GET },
produces = "application/json", headers={"accept=application/json"})
#Timed
#ExceptionMetered
#LogExecutionTime
public String getProductDetails(#PathVariable("id") String id) {
return productService.getProductDetails(id);
}
At the moment I see no way of leveraging the auto generation without putting additional effort into it. Spring Auto REST Docs works by inspecting POJOs with a Jackson visitor (static introspection without runtime information) and there is currently no way of deriving the JSON fields from a string (would be dynamic at runtime). Thus, I only see two options:
The approach that you already described: Creating the corresponding POJO and using it.
Using Spring REST Docs for the corresponding test and manually document each field in the test. Might be the better option here if you do not want to alter the production code.

Why does Spring allow controller annotated request mappings on private methods?

Just came accross this today in a Spring MVC cotnroller class,
#RequestMapping(value = { "/foo/*" }, method = { RequestMethod.GET})
private String doThing(final WebRequest request) {
...
return "jsp";
}
This is making it a bit harder to write a test, I'll probably change it to public but what's the point of allowing mappings on private methods?
Java does not provide a mechanism for limiting the target of annotations based on access modifier.
As #smp7d stated, Java does not limit the target of annotations based on access modifiers, but syntactically speaking, #RequestMapping should not work on private methods. Also we cannot limit this, since it would break the backward compatibility. So, you can either go for defining your methods as public or you can create your own custom implementation.
Take a look at this: Spring's #RequestMapping annotation works on private methods

Resources