How to write appropriate endpoint in Spring Boot for GET request? - spring-boot

I have an assignment to write simple GET request.
The format that is going to be typed in URL is like this:
http://localhost:8080/api/tasks/20-08-2020
Server should return TODOs for that date. I did managed to write a finder method. But not sure how to write an endpoint. This is what I have so far:
#GetMapping(value = "/{date}", consumes="application/json")
public ResponseEntity<List<Task>> getTasksByDateUsingURL(#PathVariable("date") #DateTimeFormat(pattern="dd-MM-yyyy") #Valid LocalDate dueDate){
List<Task> tasks = taskService.getAllTasksByDate(dueDate);
return new ResponseEntity<List<Task>>(tasks,HttpStatus.OK);
}
This is inside RestController class:
#RestController
#RequestMapping(value="/api/tasks")
public class TaskController {...}
I cannot hit this GET endpoint...

Workaround for your problem is to get the string as parameter and parse it manually
#GetMapping(value = "/{date}", consumes="application/json")
public ResponseEntity<List<Task>> getTasksByDateUsingURL(
#PathVariable("date")
String date
){
LocalDate dueDate = parse(date);
List<Task> tasks = taskService.getAllTasksByDate(dueDate);
return new ResponseEntity<List<Task>>(tasks,HttpStatus.OK);
}
private LocalDate parse(String stringDate) {
// TODO
}

As author said in comments:
When try to call the endpoint from browser, the mapping is not executed.
Seems like that the browser is sending request with wrong Content-Type header. Your mapping is explicitly requires only application/json value.
When try to call the endpoint from Postman, the application returns 400 status.
I could not see the body of response, but I guess the problem is #Valid annotation on the parameter. How should Spring validate the LocalDate?
So the solution is to remove consumes="application/json" from mapping or send corresponding Content-Type value
and remove #Valid annotation from parameter.

Related

My spring controller test doesn't enter controller

I am trying to test spring controller using mockMvc. There is existing functionality, so I referenced it as I was creating my test since I am new to spring controller. While existing test works fine, my test doesn't go into the spring controller I expected to. Here is my test:
#Test
public void updatePriorityStudyDispatch() throws Exception {
DispatchStudyPriorityRequest request = TestDataFactory.getDispatchStudyPriorityRequest();
mockMvc.perform(post(BASE_URL, getDispatchId(WORKSTATION_ID_VALUE, STUDY_ID_VALUE))
.accept(PowerShareMediaType.PSH_GATEWAY_STUDYDISPATCHER_JSON)
.contentType(MediaType.APPLICATION_JSON)
.content(gson.toJson(request)))
.andExpect(status().isOk());
verify(studyDispatcherService)
.updatePriority(WORKSTAION_ID, STUDY_ID, TestDataFactory.getDispatchStudyPriorityRequest());
}
and here is my controller:
#PostMapping(path = "/{dispatchid}", produces = PowerShareMediaType.PSH_GATEWAY_STUDYDISPATCHER_JSON)
public ResponseEntity<Void> updatePriority(#PathVariable("dispatchid") String dispatchId,
#Valid #RequestBody DispatchStudyPriorityRequest request) {
...
...
...
}
I had a break points on my controller and it seems like it never enters it when I run the test. My result is 400 when I am expecting 200. I am not sure what is happening.
The one that is working basically have different "request" object (different model). Same BASE_URL, same ID_VALUEs, same MediaTypes. Here is controller
#PutMapping(path = "/{dispatchid}", produces = PowerShareMediaType.PSH_GATEWAY_STUDYDISPATCHER_JSON)
public ResponseEntity<Void> dispatchStudy(#PathVariable("dispatchid") String dispatchId,
#Valid #RequestBody StudyDispatchRequest request) {
I think it has something to do with RequestBody, but this is first time I am using RequestBody with some object. I learned that there is spring functionality that converts incoming Json file to object. However, I am really new to this concept, so it is hard for me to understand the issue.
I found why it was happening. It was giving me 400 because Date() field in the DispatchStudyPriorityRequest object was in Gson format. Somehow Json format wasn't able to parse it correctly.
Gson format was something like "Jun 12, 2020" while Json format should have been "yyyy-mm-dd".

Spring RestController - findById and findByEmail request method not working (Ambiguous handler)

I want to retrieve user's info either based on the ID or the Email. Below is the controller class I wrote:
#RestController
#RequestMapping("/users")
public class UserController {
#Autowired
private UserDao userDao;
#GetMapping(value = "/{id:\\d+}")
public User findOneById(#PathVariable("id") final Integer userId) {
return userDao.findById(userId).get();
}
#GetMapping(value = "/{email}")
public User findOneByEmail(#PathVariable("email") final String email) {
return userDao.findByEmail(email).get();
}
The code is not working and giving error
java.lang.IllegalStateException: Ambiguous handler methods mapped for HTTP path 'http://localhost:8080/users/12223': {public com.example.persistence.model.User com.example.rest.controller.UserController.findOneById(java.lang.Integer), public com.example.persistence.model.User com.example.rest.controller.UserController.findOneByEmail(java.lang.String)}.
I thought Regex would solve this issue but unfortunately it didn't.
The reason for error I understood, but what is the way to handle such requirement?
Your problem is that http://localhost:8080/users/12223 matches both /users/{id:\\d+} and /users/{email}. 12223 is a valid parameter for both methods:
12223 matches {id:\\d+} because it has all digits
12223 matches {email} because regex expression is not specified and any parameter will match email.
Spring can't select an appropriate endpoint and gives an error: Ambiguous handler methods mapped for HTTP path.
If you try another parameter, say: http://localhost:8080/users/somebody#example.com, there will be no error. Spring will be able to find out, that somebody#example.com doesn't match id and matches email.
As JB Nizet mentioned in the comments, you have 2 ways to solve this:
Specify regex for the e-mail to match e-mail format, something like {email:.+#.+\..+}
Clarify endpoints like pDer666 recommended:
#GetMapping(value = "/email/{email}")
#GetMapping(value = "/id/{id:\d+}")
There are different ways to solve this. It is possible to provide two GetMappings with different paths or you use query parameters in only one get request. If the Email is Set you retrieve data by Email If the other is Set retrieve it by the other parameter. With this solution you can easily add more parameters to search by and you have the posibility to query your data by OR or AND without adding a new controller method.
Url : http://localhost:8080/users?email=asdf#somewhere.com OR http://localhost:8080/users?id=1234
#GetMapping
#ResponseBody
public String findOne(#RequestParam("id") long id, #RequestParam("email") String email) {
...
}

Get request body as string/json to validate with a json schema- Spring boot REST API

I'm trying to validate JSON (passed by a client as a request body) before it is converted into a model in Controller method.
If validation passes then return nothing, let the process continue as it was (spring boot to convert JSON into a model marked as #RequestBody). Throw error in case validation fails (everit-org/json-schema).
I tried to two way:
Implement HandlerMethodArgumentResolver, but resolveArgument() doesn't give request body details as it is already read and stored in ContentCachingRequestWrapper.
NOTE: inputStream in ContentCachingRequestWrapper doesn't have any request body details.
Using spring Interceptor. But this doesn't help me to find request body type passed in the request. As JSON schema is different for each request.
Any other approaches I can try with?
I cannot add a comment ... so ...
What kind of validation do you need? If you only want to validate the fields like length of a string or range of a number and so on. I recommend you use #Validated on controller mehtod parameter, and model:
#NotNull
#Size(min = 32, max = 32)
private String id;
controller:
#PatchMapping
public Object update(#RequestBody #Validated User user, Errors errors) {
...
}
If there is something wrong, errors.hasErrors() will return true.
edit:
OK, I did some tests, in a filter :
HttpServletRequest httpServletRequest = (HttpServletRequest)request;
ServletInputStream inputStream = httpServletRequest.getInputStream();
byte[] a = new byte[1024];
inputStream.read(a);
System.out.println(IOUtils.toString(a));
I got a json string (a piece of request body) :
{"template":"5AF78355A4F0D58E03CE9F55AFA850F8","bd":"" ...

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

Parsing JSON request body with Spring MVC

I am using Spring 4.1 framework for developing webservices. When I return a Java object as response, it is automatically converted to JSON and delivered to client, so I assume that JSON parser is in classpath and it is configured properly. However it fails to convert the request body from JSON into Java object and client is getting a HTTP response of 400.
Here is how the webservice looks like:
public class Details{
public Details(){
}
int code;
int area;
}
#RequestMapping(value = "/api/update/{phoneNumber}", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> update(#PathVariable final String phoneNumber, #RequestBody Details details)
Here is how the request looks like:
Method: Post
Content-Type: application/json; charset=utf-8
Body: {"code":0,"area":12}
If I collect the request body as string and parse it manually then it works, so it gets the valid JSON but for some reason it is not parsing it automatically. I have no clue on how to fix it. Please help. Thanks in advance.
You have package-private properties in your Details class, so they are probably not recognised by json-converter.
You have several options:
define them as public (not recommended)
provide getters and setters
if you are using jackson, you can annotate them with #JsonProperty, leaving them package-private
Finally I got the reason for this. I was using inner classes which were not static. Making those static fixed the issue.

Resources