create two method for same url pattern with different arguments - spring

I have scenario where one url "serachUser" may come with two different value (request parameter) userId or UserName.
so for this I have created two methods
public String searchUserById(#RequestParam long userID, Model model)
public ModelAndView searchUserByName(#RequestParam String userName)
But i am getting Ambiguous mapping found exception. Can Spring handle this situation?

You can use the params parameter to filter by HTTP parameters. In your case it would be something like:
#RequestMapping(value = "/searchUser", params = "userID")
public String searchUserById(#RequestParam long userID, Model model) {
// ...
}
#RequestMapping(value = "/searchUser", params = "userName")
public ModelAndView searchUserByName(#RequestParam String userName) {
// ...
}

Any way incase of request param null is allowed if you don't pass any value it will be null then you can write your coad like:
#RequestMapping(value = "/searchUser", params = {"userID","userName"})
public String searchUserById(#RequestParam long userID,#RequestParam String
userName,
Model model) {
if(userID != null){
//..
}else{
// ...
}

Related

Two #GetMapping with same URL but different parameters

I'm learning Spring MVC and while in process I came across this problem:
// http://localhost:8080/todo-list/welcomeWithParam?user=Stefan
#GetMapping("welcomeWithParam")
public String welcome91(#RequestParam String user, Model model) {
model.addAttribute("helloThroughParam", demoService.getHelloMessage(user));
return "welcome-with-model";
}
// http://localhost:8080/todo-list/welcomeWithParam?user=Stefan&age=31
#GetMapping("welcomeWithParam")
public String welcome92(#RequestParam String user, #RequestParam int age, Model model) {
model.addAttribute("helloThroughParam", demoService.getHelloMessage(user));
model.addAttribute("age", age);
return "welcome-with-model";
}
I'm getting this error:
Caused by: java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'demoController' method
academy.learnprogramming.controller.DemoController#welcome92(String, int, Model)
to {GET [/welcomeWithParam]}: There is already 'demoController' bean method
academy.learnprogramming.controller.DemoController#welcome91(String, Model) mapped.
So, Spring is telling me we cant have two GET mappings with same URL but different number/type of parameters?
If I change value of one of #GetMapping("") values, it works fine.
Something like this:
#RequestMapping(value = "/welcomeWithParam", params = "user")
public String welcome91(#RequestParam String user, Model model) {
// ...
}
#RequestMapping(value = "/welcomeWithParam", params = {"user","age"})
public ModelAndView welcome92(#RequestParam String user, #RequestParam int age, Model model) {
// ...
}

Can spring map POST parameters by a way other than #RequestBody

I am using #RestControllers with an application where all requests are POST requests ... As I learned from this post , you can't map individual post parameters to individual method arguments, rather you need to wrap all the parameters in an object and then use this object as a method parameter annotated with #RequestBody thus
#RequestMapping(value="/requestotp",method = RequestMethod.POST)
public String requestOTP( #RequestParam(value="idNumber") String idNumber , #RequestParam(value="applicationId") String applicationId) {
return customerService.requestOTP(idNumber, applicationId);
will not work with a POST request of body {"idNumber":"345","applicationId":"64536"}
MY issue is that I have A LOT of POST requests , each with only one or two parameters, It will be tedious to create all these objects just to receive the requests inside ... so is there any other way similar to the way where get request parameters (URL parameters) are handled ?
Yes there are two ways -
first - the way you are doing just you need to do is append these parameter with url, no need to give them in body.
url will be like - baseurl+/requestotp?idNumber=123&applicationId=123
#RequestMapping(value="/requestotp",method = RequestMethod.POST)
public String requestOTP( #RequestParam(value="idNumber") String idNumber , #RequestParam(value="applicationId") String applicationId) {
return customerService.requestOTP(idNumber, applicationId);
second- you can use map as follows
#RequestMapping(value="/requestotp",method = RequestMethod.POST)
public String requestOTP( #RequestBody Map<String,Object> body) {
return customerService.requestOTP(body.get("idNumber").toString(), body.get("applicationId").toString());
I have change your code please check it
DTO Class
public class DTO1 {
private String idNumber;
private String applicationId;
public String getIdNumber() {
return idNumber;
}
public void setIdNumber(String idNumber) {
this.idNumber = idNumber;
}
public String getApplicationId() {
return applicationId;
}
public void setApplicationId(String applicationId) {
this.applicationId = applicationId;
}
}
Rest Controller Method
#RequestMapping(value="/requestotp",method = RequestMethod.POST)
public String requestOTP( #RequestBody DTO1 dto){
System.out.println(dto.getApplicationId()+" (------) "+dto.getIdNumber());
return "";
}
Request Type -- application/json
{"idNumber":"345","applicationId":"64536"}
OR
#RequestMapping(value="/requestotp",method = RequestMethod.POST)
public String requestOTP( #RequestBody String dto){
System.out.println(dto);
return "";
}

Spring Boot - How can I pass custom values in HTTP Post api?

I'm new with Spring Boot and I have difficult to understand how can I pass data. For example:
I want pass those data to my server:
{
"code", 1,
"name": "C01"
}
So I have create always a custom Object with code and name as attributes to have this http post api?
#RequestMapping(value = "/new/", method = RequestMethod.POST)
public ResponseEntity<?> createOrder(#RequestBody CustomObject customObject){
...
}
Another solution I see that can be this but I can't pass numbers (int code), right?
#RequestMapping(value = "/new/{code}/{name}", method = RequestMethod.POST)
public ResponseEntity<?> createOrder(#PathVariable("code") int code, #PathVariable("name") String name) {
...
}
Kind regards :)
You can pass code and name as PathVariables just like in your example:
#RequestMapping(value = "/new/{code}/{name}")
public ResponseEntity<?> createOrder(#PathVariable("code") int code, #PathVariable("name") String name) {
...
}
A PathVariable can be an int or a String or a long or a Date, according to the docs:
A #PathVariable argument can be of any simple type such as int, long, Date, etc. Spring automatically converts to the appropriate type or throws a TypeMismatchException if it fails to do so.
You could also define a PathVariable of type Map<String, Object> like this:
#RequestMapping(value = "/new/{code}/{name}")
public ResponseEntity<?> createOrder(#PathVariable("map") Map<String, Object> map) {
Integer code = (Integer) map.get("code");
String name = (String) map.get("name");
...
}
You could even use #RequestParam and supply the data in the form of URL query parameters.
So, there are numerous ways in which data can be passed to a Spring MVC controller (more details in the docs) but I think the convention for posting complex data (by "complex" I mean more than a single piece of state) is to define a request body which contains a serialised form of that complex state i.e. what you showed in the first example in your queston:
#RequestMapping(value = "/new/", method = RequestMethod.POST)
public ResponseEntity<?> createOrder(#RequestBody CustomObject customObject){
...
}
If this question is about RESTful best practice, since you are developing webservice for creating an Order object, this is how I would design it
Order.java
public class Order {
private Integer code;
private String name;
public Integer getCode() {
return code;
}
public void setCode(final Integer code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
}
Controller
#RequestMapping(value = "/orders", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public ResponseEntity<Order> createOrder(#Valid #RequestBody Order order){
...
}
Technically, you can do many things to achieve the same thing, but that will not be a RESTful service, it will be an RPC at best.

Conditional validation in spring mvc

Now I have following controller method signature:
#ResponseBody
#RequestMapping(value = "/member/createCompany/addParams", method = RequestMethod.POST)
public ResponseEntity setCompanyParams(
#RequestParam("companyName") String companyName,
#RequestParam("email") String email,
HttpSession session, Principal principal) throws Exception {...}
I need to add validation for input parameters.
Now I am going to create object like this:
class MyDto{
#NotEmpty
String companyName;
#Email // should be checked only if principal == null
String email;
}
and I am going to write something like this:
#ResponseBody
#RequestMapping(value = "/member/createCompany/addParams", method = RequestMethod.POST)
public ResponseEntity setCompanyParams( MyDto myDto, Principal principal) {
if(principal == null){
validateOnlyCompanyName();
}else{
validateAllFields();
}
//add data to model
//return view with validation errors if exists.
}
can you help to achieve my expectations?
That's not the way Spring MVC validations work. The validator will validate all the fields and will put its results in a BindingResult object.
But then, it's up to you to do a special processing when principal is null and in that case look as the validation of field companyName :
#ResponseBody
#RequestMapping(value = "/member/createCompany/addParams", method = RequestMethod.POST)
public ResponseEntity setCompanyParams(#ModelAttribute MyDto myDto, BindingResult result,
Principal principal) {
if(principal == null){
if (result.hasFieldErrors("companyName")) {
// ... process errors on companyName Fields
}
}else{
if (result.hasErrors()) { // test any error
// ... process any field error
}
}
//add data to model
//return view with validation errors if exists.
}

Mock MVC - Add Request Parameter to test

I am using spring 3.2 mock mvc to test my controller.My code is
#Autowired
private Client client;
#RequestMapping(value = "/user", method = RequestMethod.GET)
public String initUserSearchForm(ModelMap modelMap) {
User user = new User();
modelMap.addAttribute("User", user);
return "user";
}
#RequestMapping(value = "/byName", method = RequestMethod.GET)
#ResponseStatus(HttpStatus.OK)
public
#ResponseBody
String getUserByName(
#RequestParam("firstName") String firstName,
#RequestParam("lastName") String lastName,
#ModelAttribute("userClientObject") UserClient userClient) {
return client.getUserByName(userClient, firstName, lastName);
}
and I wrote following test:
#Test public void testGetUserByName() throws Exception {
String firstName = "Jack";
String lastName = "s";
this.userClientObject = client.createClient();
mockMvc.perform(get("/byName")
.sessionAttr("userClientObject", this.userClientObject)
.param("firstName", firstName)
.param("lastName", lastName)
).andExpect(status().isOk())
.andExpect(content().contentType("application/json"))
.andExpect(jsonPath("$[0].id").exists())
.andExpect(jsonPath("$[0].fn").value("Marge"));
}
what i get is
java.lang.AssertionError: Status expected:<200> but was:<400>
at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:60)
at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:89)
at org.springframework.test.web.servlet.result.StatusResultMatchers$5.match(StatusResultMatchers.java:546)
at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:141)
Why this happens? Is it right way to pass the #RequestParam
When i analyzed your code. I have also faced the same problem but my problem is if i give value for both first and last name means it is working fine. but when i give only one value means it says 400. anyway use the .andDo(print()) method to find out the error
public void testGetUserByName() throws Exception {
String firstName = "Jack";
String lastName = "s";
this.userClientObject = client.createClient();
mockMvc.perform(get("/byName")
.sessionAttr("userClientObject", this.userClientObject)
.param("firstName", firstName)
.param("lastName", lastName)
).andDo(print())
.andExpect(status().isOk())
.andExpect(content().contentType("application/json"))
.andExpect(jsonPath("$[0].id").exists())
.andExpect(jsonPath("$[0].fn").value("Marge"));
}
If your problem is org.springframework.web.bind.missingservletrequestparameterexception you have to change your code to
#RequestMapping(value = "/byName", method = RequestMethod.GET)
#ResponseStatus(HttpStatus.OK)
public
#ResponseBody
String getUserByName(
#RequestParam( value="firstName",required = false) String firstName,
#RequestParam(value="lastName",required = false) String lastName,
#ModelAttribute("userClientObject") UserClient userClient)
{
return client.getUserByName(userClient, firstName, lastName);
}
If anyone came to this question looking for ways to add multiple parameters at the same time (my case), you can use .params with a MultivalueMap instead of adding each .param :
LinkedMultiValueMap<String, String> requestParams = new LinkedMultiValueMap<>()
requestParams.add("id", "1");
requestParams.add("name", "john");
requestParams.add("age", "30");
mockMvc.perform(get("my/endpoint").params(requestParams)).andExpect(status().isOk())
#ModelAttribute is a Spring mapping of request parameters to a particular object type. so your parameters might look like userClient.username and userClient.firstName, etc. as MockMvc imitates a request from a browser, you'll need to pass in the parameters that Spring would use from a form to actually build the UserClient object.
(i think of ModelAttribute is kind of helper to construct an object from a bunch of fields that are going to come in from a form, but you may want to do some reading to get a better definition)

Resources