REST API design - optional request parameters - spring

I've written this request mapping to access a ticket by it's id:
#GetMapping(path = "/tickets/{ticketId}")
#ResponseStatus(value = HttpStatus.OK)
public ResponseEntity<List<TicketResponse>> getTicketsById(#PathVariable("ticketId") final Long ticketId
I'm planning to add multiple query parameters to support filtering such as ticketType, ticketStatus. REST API users should have options to filter on any or all of the query parameters.
What are the REST API design principles to achieve this ? Should I add new request parameters to support the filtering like below ? :
#GetMapping(path = "/tickets/{ticketId}")
#ResponseStatus(value = HttpStatus.OK)
public ResponseEntity<List<TicketResponse>> getTicketsById(#PathVariable("ticketId") final Long ticketId, #RequestParam("ticketType") final String ticketType, #RequestParam("ticketStatus") final String ticketStatus)
Is there a Spring design pattern for this scenario ? The Java builder pattern could be used where parameter an attribute of a QueryParameter object ?

You basically have two options:
Either you put all your RequestParams as method parameters, but with required=false, like #AmitKumar wrote. Example: #RequestParam(name="ticketType", required = false) String ticketType
Put all these parameters into a, let's say FilterDTO and have that as a parameter (FilterDTO filter). Spring will make sure to populate its fields with your RequestParams . Just put your ticketType and other parameters into the DTO as fields, and they will be optional. Example: public ResponseEntity<List<TicketResponse>> getTicketsById(#PathVariable("ticketId") ong ticketId, FilterDto filter){}

If you want to make parameter as an optional. you need to add required=false.
public ResponseEntity<List<TicketResponse>> getTicketsById(#PathVariable("ticketId") final Long ticketId, #RequestParam(name="ticketType", required=false) final String ticketType, #RequestParam(name="ticketStatus",required=false) final String ticketStatus)

Related

Populate query parameters from DTO

Is there a way to let Spring populate RestTemplate query parameters automatically from a DTO, similarly to how it instantiates the response DTO automatically?
I wish to write something like:
RequestDto request = new RequestDto();
request.setFoo("foo");
request.setBar("bar");
ResponseDto response = restTemplate.getForObject(
"http://example.com/api",
ResponseDto.class,
request
);
Instead of:
ResponseDto response = restTemplate.getForObject(
"http://example.com/api?foo={foo}&bar={bar}",
ResponseDto.class,
"foo",
"bar"
);
Because there are many large DTOs, requiring tons of boilerplate code, which must be kept in sync with any DTO changes.
Spring 4.3.25
I don't think that is directly possible. The following is not exactly using the DTO, but it does let you build the request without having to form the URL string manually. You can use Spring's UriComponentsBuilder class.
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl("http://example.com/api")
.queryParam("foo", "bar")
// etc. ...
.queryParam("bar", "foo");
String result = restTemplate.getForObject(builder.toString(), String.class);
You could loop over the DTO and build the query as above. Or without the DTO, you could use a Map<String, String> and loop over it.
Map<String, String> params = new HashMap<>();
params.put("foo", "bar");
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl("http://example.com/api");
for (Map.Entry<String, String> entry : params.entrySet()) {
builder.queryParam(entry.getKey(), entry.getValue());
}
String result = restTemplate.getForObject(builder.toString(), String.class);
Edit:
As crizzis suggested below, you can use Spring Cloud OpenFeign's REST Client (from Feign #QueryMap support):
The OpenFeign #QueryMap annotation provides support for POJOs to be used as GET parameter maps. Unfortunately, the default OpenFeign QueryMap annotation is incompatible with Spring because it lacks a value property.
and
Spring Cloud OpenFeign provides an equivalent #SpringQueryMap annotation, which is used to annotate a POJO or Map parameter as a query parameter map.
From your question's example:
public class RequestDto {
private string foo;
private string bar;
}
#FeignClient(name = "client", url = "http://example.com")
public interface FooTemplate {
#GetMapping(path = "/api")
String endpoint(#SpringQueryMap RequestDto requestDto);
}
You can do something like this-
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("http://example.com/api")
.queryParam("foo", "foo")
.queryParam("bar", "bar");
ResponseDto response = restTemplate.getForObject(
builder.buildAndExpand(builder).toUriString(),
ResponseDto.class);
A more detailed answer can be found here- RestTemplate: How to send URL and query parameters together
How about using Feign? It allows you to describe the remote endpoint just like a Spring Controller. This includes support for query parameter DTOs.
See an example here

how to capture some parameters using #RequestParam using spring mvc?

Suppose a hyperlink is clicked and an url is fired with the following parameter list myparam1=myValue1&myparam2=myValue2&myparam3=myValue3 . Now how can I capture some of the parameters using #RequestParam in spring mvc?
My requirement is I have to capture some of the params and build the request to server. can I make all the request params as optional and used when required?
Suppose I want to use first two params and want to ignore the third.
For eg. http://localhost:8080/api?myparam1=myValue1&myparam2=myValue2 and just not giving 3rd parameter in request.
In the next scenario, I want to use second and third and want to ignore the first parameter.
For eg. http://localhost:8080/api?myparam2=myValue2&myparam3=myValue3 and just not giving 1st parameter in request.
In another scenario, I don't want to use any of the request param.
For eg. http://localhost:8080/api and just not giving any parameters in request.
is there any way I could achieve this? Please help...!
You can capture all the params in a Map (the key is the name of the param) like this :
public void requestAllParams(#RequestParam Map<String, String> params)
You can also capture optional param using Optional like this :
public void requestParamOptional(#RequestParam(required=false) Optional<String> param)
A parameter with #RequestParam is by default required. It can be marked as not required:
#GetMapping
public ResponseEntity<Object> aMapping(#RequestParam String myparam1, #RequestParam String myparam2, #RequestParam(required = false) String myparam3) {
// response
}

#RequestParam and #PathVariable on the same variable in Spring Boot

In my Spring boot microservice I need to have dedicated endpoint that maps next urls
/delivery-options/myLabel
/delivery-options?label=myLabel
For handling both I try to use both #RequestParam and #PathVariable for my controller's method parameter but it does not work for both situations
#RequestMapping({"/delivery-options", "/delivery-options/{label}"})
public ResponseEntity<?> getDeliveryOptions(#RequestParam(value = "label", required = false) #PathVariable(value = "label", required = false) String label ) {
}
Is it possible to map both to one variable?
I believe we can only do this by assigning to two different variables and making both required to false.
I know this is not the solution you are looking for and also i believe the other way is declaring two different methods and mapping each request to one of them, even though that not makes the duplicate code because you are handling request and mapping to service layer based on input
#RequestMapping(value= {"/hello/{hi}","/hellodiff"}, method=RequestMethod.GET)
public void hello(#PathVariable(value="hi", required=false) String hi,
#RequestParam(value="key", required=false) String key) {
System.out.println("Output: "+ hi +" "+ key);
}
Output:
localhost:8080/hello/hey
Output: hey null
localhost:8080/hellodiff?key="cool"
Output: null "cool"

Issue with Spring Rest #RequestMapping when negating params

I have two spring controller methods :
#RequestMapping(value="/requestotp",method = RequestMethod.POST,params = "!applicationId") //new customer
public OTPResponseDTO requestOTP( #RequestBody CustomerDTO customerDTO){
return customerService.requestOTP(customerDTO);
}
#RequestMapping(value="/requestotp",method = RequestMethod.POST,params = {"idNumber","applicationId"}) //existing customer
public String requestOTP( #RequestParam(value="idNumber") String idNumber , #RequestParam(value="applicationId") String applicationId) {
return customerService.requestOTP(idNumber, applicationId);
}
using "!applicationId" , I am expecting that when I call the url with applicationId parameter there that the second method will be called , but actually when I pass a request like this :
{"idNumber":"345","applicationId":"64536"}
The first method gets called
This is the part of the params paremeters documentation that I rely on :
Finally, "!myParam" style expressions indicate that the specified
parameter is not supposed to be present in the request.
Can't you just simply delete first request params?
#RequestMapping(value="/requestotp",method = RequestMethod.POST) //new customer
public OTPResponseDTO requestOTP( #RequestBody CustomerDTO customerDTO){
return customerService.requestOTP(customerDTO);
}
The issue actually wasn't with negating the parameter, the issue was that I was sending {"idNumber":"345","applicationId":"64536"} in the POST body and I was expecting the variables to be mapped to the method parameters annotated with #RequestParam ... this is not correct ... #RequestParam only map URL parameters .... so the controller was trying to find the best match so it was using the first method as it contained #RequestBody

how to capture multiple parameters using #RequestParam using spring mvc?

Suppose a hyperlink is clicked and an url is fired with the following parameter list myparam=myValue1&myparam=myValue2&myparam=myValue3 . Now how can I capture all the parameters using #RequestParam in spring mvc?
My requirement is I have to capture all the params and put them in a map.
Please help!
#RequestMapping(value = "users/newuser", method = RequestMethod.POST)
public String saveUser(#RequestParam Map<String,String> requestParams) throws Exception{
String userName=requestParams.get("email");
String password=requestParams.get("password");
//perform DB operations
return "profile";
}
You could use RequestParam in the above mentioned manner.
It seems you can't get
Map<String,String>
because all your params have same name "myparam"
Try this instead:
public ModelAndView method(#RequestParam("myparam") List<String> params) { }
To get all parameters at once try this:
public ModelAndView postResultPage(#RequestParam MultiValueMap<String, String> params)
This feature is described in the #RequestParam java doc (3. Paragraph):
Annotation which indicates that a method parameter should be bound to a web request parameter. Supported for annotated handler methods in Servlet and Portlet environments.
If the method parameter type is Map and a request parameter name is specified, then the request parameter value is converted to a Map assuming an appropriate conversion strategy is available.
If the method parameter is Map<String, String> or MultiValueMap<String, String> and a parameter name is not specified, then the map parameter is populated with all request parameter names and values.
As of Spring 3.0, you can also use MultiValueMap to achieve this:
A rudimentary example would be:
public String someMethod(#RequestParam MultiValueMap<String,String> params) {
final Iterator<Entry<String, List<String>>> it = params.entrySet().iterator();
while(it.hasNext()) {
final String k = it.next().getKey();
final List<String> values = it.next().getValue();
}
return "dummy_response";
}
If anyone is trying to do the same in Spring Boot, use RequestBody in place of RequestParam
Spring mvc can support List<Object>, Set<Object> and Map<Object> param, but without #RequestParam.
Take List<Object> as example, if your object is User.java, and it like this:
public class User {
private String name;
private int age;
// getter and setter
}
And you want pass a param of List<User>, you can use url like this
http://127.0.0.1:8080/list?users[0].name=Alice&users[0].age=26&users[1].name=Bob&users[1].age=16
Remember to encode the url, the url after encoded is like this:
http://127.0.0.1:8080/list?users%5B0%5D.name=Alice&users%5B0%5D.age=26&users%5B1%5D.name=Bob&users%5B1%5D.age=16
Example of List<Object>, Set<Object> and Map<Object> is displayed in my github.
You can use for multiple Params as such
public String saveUser(#RequestParam("email") String userName, #RequestParam("password") String password) throws Exception{
//your code
//perform DB operations
return "profile";
}
For params with same name, you can use MultiValueMap<String ,String>. Then all the values would be present as List
You can use multiple #RequestParam annotations as shown below.
#RequestParam(value="myparam1", required = true) <Datatype> myparam1,
#RequestParam(value = "myparam2", required = false) <Datatype> myparam2,

Resources