Order of different Spring controller methods by params - spring-boot

There are two methods in the following controller with different params values.
#RestController
#RequestMapping("/api/v1")
public class ToyApiController {
#GetMapping(value = "/toys", params = {"toySize=large"}, produces = MediaType.APPLICATION_JSON_VALUE)
public ToysVO listLargeToys(ListToyCommand listToyCommand) {
// ...
}
#GetMapping(value = "/toys", params = {"country"}, produces = MediaType.APPLICATION_JSON_VALUE)
public ToysVO listToysOfCountry(ListToyCommand listToyCommand) {
// ...
}
}
The API below is expected to hit the listLargeToys method. The behavior is correct.
GET /api/v1/toys?toySize=large&max=10
The API below is expected to hit the listToysOfCountry method. However, the listLargeToys method is hit.
GET /api/v1/toys?country=france&toySize=large&max=10
The code in this post is simplified from actual code. I know that the two methods can be combined into a single method that checks the toySize and country parameters and returns different values accordingly. I'm just wondering whether the listToysOfCountry method can be hit by some tiny changes, e.g. specifying parameters like params without combining the two methods.
GET /api/v1/toys?country=france&toySize=large&max=10

I guess you need some like this:
#RequestMapping(
value = "/toys",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody ResponseEntity<String> toys(
#RequestParam(value = "toySize", required = false) String toySize,
#RequestParam(value = "country", required = false) String country,
#RequestParam(value = "max", required = false) Integer max) {
if(null != max && max == 10) {
System.out.println("You send 10 dude.");
}
return new ResponseEntity<String>("{ \"toySize\": "+ toySize +", \"country\": "+ country +", \"max\": "+ max +"}", HttpStatus.OK);
}
Using "required = false", you make param optional :)

By adding a !country in params for listLargeToys API, GET /api/v1/toys?country=france&toySize=large&max=10 no longer goes into listLargeToys API. Instead, it goes into listToysOfCountry as expected.
#RestController
#RequestMapping("/api/v1")
public class ToyApiController {
#GetMapping(value = "/toys", params = {"toySize=large", "!country"}, produces = MediaType.APPLICATION_JSON_VALUE)
public ToysVO listLargeToys(ListToyCommand listToyCommand) {
// ...
}
#GetMapping(value = "/toys", params = {"country"}, produces = MediaType.APPLICATION_JSON_VALUE)
public ToysVO listToysOfCountry(ListToyCommand listToyCommand) {
// ...
}
}

Related

Spring responds 400 with array query param

I have the following spring boot #RestController request mapping:
#RequestMapping({"/api/blog"})
#RestController
public class BlogController {
#RequestMapping(value = "/test", method = RequestMethod.GET)
public Iterable<Blog> filterBy(
#RequestParam(required = false, name = "filter") String filterStr,
#RequestParam(required = false, name = "range") String rangeStr,
#RequestParam(required = false, name="sort") String sortStr) {
...
}
}
The request should look like so:
GET http://my.api.url/api/blog/test?sort=['title','ASC']&range=[0, 24]&filter={title:'bar'}
However providing any of the array query parameters (range and/or sort) causes a response of 400 with no details other than: "HTTP Status 400 - Bad Request"
Making a request with only the filer query parm works. Adding range and/or sort with null values works. Soon as i add the brackets [] it appears to fail.
I have tried to add the following Exception handler to debug the problem to both the controller and a ControllerAdvice class:
#ExceptionHandler
#ResponseStatus(HttpStatus.BAD_REQUEST)
public void handle(HttpMessageNotReadableException e) {
logger.warn("Returning HTTP 400 Bad Request", e);
}
However this does not get triggered.
I have a suspicion that something is happening in the framework causing the 400 before it even gets to the controller.
Any help is appreciated.
Try defining your parameters as Lists and dont use brackets.
#RequestMapping(value = "/test", method = RequestMethod.GET)
public Iterable<Blog> filterBy(
#RequestParam(required = false, name = "filter") List<String> filterStr,
#RequestParam(required = false, name = "range") List<String> rangeStr,
#RequestParam(required = false, name = "sort") List<String> sortStr) {
filterStr.forEach(s -> System.out.print(", "+ s));
System.out.println();
rangeStr.forEach(s -> System.out.print(", "+ s));
System.out.println();
sortStr.forEach(s -> System.out.print(", "+ s));
System.out.println();
return new ArrayList<>();
}
// test url with mockmvc
#Test
public void filterBy() throws Exception {
mockMvc.perform(get("/test?filter=1,2,3,4&range=5,7,8&sort=desc"))
.andExpect(status().is2xxSuccessful());
}
#Test
public void filterBy() throws Exception {
mockMvc.perform(get("/test?filter=1&filter=2&filter=3&filter=4&range=[5,7,8]&sort=desc"))
.andExpect(status().is2xxSuccessful());
}
For me the first test prints:
, 1, 2, 3, 4
, 5, 7, 8
, desc
The second test prints:
, 1, 2, 3, 4
, [5, 7, 8] // brackets dont seem to work that good
, desc
you have to URL encode your parameters! when you test, you should URL encode the filter, the range and the sort parameters. Try the following:
http://my.api.url/api/blog/test?sort%3D%5B%27title%27%2C%27ASC%27%5D%26range%3D%5B0%2C+24%5D%26filter%3D%7Btitle%3A%27bar%27%7D

How test Post request with custom object in content type application/x-www-form-urlencoded?

I have controller:
#PostMapping(value = "/value/", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public String updateSettings(final Dto dto) {
System.out.println(">>> " + dto);
return "template";
}
Controller works if I send request across chrome window. But when I write test for this method I get problem. Not converted object, value not inserted.
Test:
#Test
#WithMockUser(username = FAKE_VALID_USER, password = FAKE_VALID_PASSWORD)
public void test_B_CreateDtoWithValidForm() throws Exception {
final Dto dto = new Dto();
dto.setId("value");
dto.setEnabled("true");
this.mockMvc.perform(post(URL_SET_PROVIDER_SETTINGS)
.contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.content(dto.toString()))
.andDo(print());
}
Output is >>> Dto{id=null, enabled=false}
How test Post request with custom object in content type application/x-www-form-urlencoded?
In this case you don't need to use content, but instead you need to use param in this way:
this.mockMvc.perform(post(URL_SET_PROVIDER_SETTINGS)
.contentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.param("id", "value")
.param("enabled", "true"))
.andDo(print());

Ambiguous mapping error

I am working on a Spring MVC project in IntelliJ, hosted on Tomcat 6.0.44, using JDK 1.8.0_60. My whole team is running our application without any issues. On my machine, when I try to run the latest code from Git, the same code the rest of the team is running, I get this error:
Context initialization failed Ambiguous mapping. Cannot map 'CartValidate' method.
There are 2 CartValidate methods (one POST, one GET), and a view method in the same controller that seem to be causing this issue.
If I change the request mappings for these 3 methods (I don't have to change the method names, just the request mappings), then it works fine.
I've searched the full codebase for the request mapping values, and they don't occur anywhere else, so I can't understand why there would be a problem with the original request mappings. The original request mappings are cart/validate.json and cart/view.json.
Does anyone have any idea what might be going on?
Here is the code for the 3 methods for which I need to change the request mapping to make the code run:
#ResponseBody
#RequestMapping(value = "/cart/view.json", method = RequestMethod.GET, produces = "application/json", consumes = "application/json")
public Order view(#RequestParam(value = "orderId") Long orderId,
HttpServletRequest aRequest, HttpServletResponse aResponse) {
Order order = orderService.orderGet(aRequest, aResponse, orderId);
return order;
}
#ResponseBody
#RequestMapping(value = "/cart/validate.json", method = RequestMethod.POST, consumes = "application/json", produces = "application/json")
public OrderWeb CartValidate(#RequestBody OrderWeb aModel,
HttpServletRequest aRequest, HttpServletResponse aResponse) {
String orderId = aModel.getOrderId().toString();
OrderWeb order = orderService.orderValidatePost(aModel, orderId,
aRequest, aResponse);
return order;
}
#ResponseBody
#RequestMapping(value = "/cart/validate.json", method = RequestMethod.GET, consumes = "application/json", produces = "application/json")
public OrderWeb CartValidate(#RequestParam(value = "orderId", required = true) Long orderId,
HttpServletRequest aRequest, HttpServletResponse aResponse) {
OrderWeb aModel = (OrderWeb) orderService.orderGet(aRequest, aResponse, orderId);
OrderWeb order = orderService.orderValidatePost(aModel, orderId.toString(),
aRequest, aResponse);
return order;
}

Swagger Springfox annotations not working

These are the method headers for the sample application I'm trying to make. Unfortunately I don't see the #ApiOperation or #ApiResponse or #ApiResponses annotations taking effect.
I also have a sample class and an Application class(which contains swagger configuration info and the like)
#Api(value = "/v1/", description = "Test API", produces = "application/json")
#RestController
#RequestMapping("/v1/")
class SampleRestController {
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Successfully added"),
#ApiResponse(code = 416, message = "List is empty") })
#RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE, value = "samples/{sb}",
method = RequestMethod.GET)
public Map<String, String> sbs(#PathVariable("sb") String sb) {
}
#ApiOperation(
value = "Seeing where this shows up",
notes = "we should see this in the implememtation notes",
response = Sample.class,
httpMethod = "GET"
)
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Successfully added"),
#ApiResponse(code = 416, message = "List is empty") })
#RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE, value = "samples/{pb}",
method = RequestMethod.GET)
public Sample ps(#PathVariable String pb) {
}
#RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE, value = "samples",
method = RequestMethod.GET)
public Collection<Map<String, String>> cs(#RequestParam(value = "cid", required = true, defaultValue = "")
String cid) {
}
}
Here is the relevant portion of my swagger json
{"swagger":"2.0","info":{"description":"All about the Samples","title":"Samples API","contact":{},"license":{}},"host":"localhost:8080","basePath":"/","tags":[{"name":"sample-rest-controller","description":"Sample Rest Controller"}],
"paths":{
"/v1/samples":{"get":{"tags":["sample-rest-controller"],"summary":"cs","operationId":"csUsingGET","consumes":["application/json"],"produces":["application/json"],"parameters":[{"name":"cid","in":"query","description":"cid","required":true,"type":"string"}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/Collection«Map«string,string»»"}},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"}}}},
"/v1/samples/{pb}":{"get":{"tags":["sample-rest-controller"],"summary":"ps","operationId":"psUsingGET","consumes":["application/json"],"produces":["application/json"],"parameters":[{"name":"pb","in":"path","description":"pb","required":true,"type":"string"}],"responses":{"200":{"description":"OK","schema":{"$ref":"#/definitions/Sample"}},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"}}}},
"/v1/samples/{sb}":{"get":{"tags":["sample-rest-controller"],"summary":"sbs","operationId":"sbsUsingGET","consumes":["application/json"],"produces":["application/json"],"parameters":[{"name":"sb","in":"path","description":"sb","required":true,"type":"string"}],"responses":{"200":{"description":"OK","schema":{"type":"object","additionalProperties":{"type":"string"}}},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"}}}}},
I'm using springfox-swagger2 version 2.1.1 and I'm viewing my UI through the default UI provided by swagger at their live demo app.
Can someone tell me what I'm doing wrong?
Thanks!
I was playing around and changed my import from
import com.wordnik.swagger.annotations.;
to
import io.swagger.annotations.;
and all the annotations appear to work.
So, looks like the com.wordnik.swagger.annotations import no longer works, or at least doesn't work properly?
Confirmed by Dilip Krish here

Getting 400 Bad Request during POST string in spring mvc

i have a rest api that accept a String in POST and return an object,
this is the method:
#RequestMapping(method = RequestMethod.POST, value = "/aValue", headers = "Accept=application/json")
public #ResponseBody
MyObject getMyObject(#RequestBody String string) {
MyObject response = myService.getMyObject(string);
return response;
}
now when i call the api from another service for example, if I do POST like this it gave me always 400 Bad Request:
List<Object> providers = new ArrayList<Object>();
providers.add(jsonProvider);
WebClient client = WebClient.create(baseUrl + myAPI, providers);
client.type(MediaType.APPLICATION_JSON);
client.accept(MediaType.APPLICATION_JSON);
MyObject response = client.post(userId, MyObject.class);
return response;
instead of the working solution i used which is this one:
MyObject response = client.post("\"" + userId + "\"", MyObject.class);
someone could help me ? thanks guys
You're having an issue 'cause what you're posting is not a valid JSON, yet you indicate that it is in your client-side code. As you seem to pass just a simple string property userId you can simply change your mapping to receive plain text by adding consumes = "text/plain",
#RequestMapping(method = RequestMethod.POST, value = "/aValue", headers = "Accept=application/json", consumes = "text/plain")
public #ResponseBody
MyObject getMyObject(#RequestBody String string) {
and have your client send plain text, so
client.type(MediaType.TEXT_PLAIN);

Resources