Spring multiple #PathVariable failure - spring

when I try calling this endpoint with just this endpoint: /api/v1/data/{provider}/{subject} (I removed #pathvariable dataset parameter when I got rid of the dataset path) I am able to successfully call my API. However, when I add an additional pathvariable (dataset in this case), I can't seem to hit my endpoint anymore. Is there some restriction on number of path variables? Not sure what I'm doing wrong.
#GetMapping("/api/v1/data/{provider}/{subject}/{dataset}")
public List<List<String>> getEDXDatasetHead(
#PathVariable final String provider,
#PathVariable final String subject,
#PathVariable final String dataset,
#RequestParam(required = false, value = "date") final String date
)
https://localhost:8443/api/v1/data/testprovider/testsubject/testdataset
#GetMapping("/api/v1/data/{provider}/{subject}/{dataset}")

There is no particular limit on number of PathVariable that you can add to a method.
But if you want to call your API with many different number of PathVariables you need to add a method for each of them, otherwise spring is not able to understand that you are calling the same method with a shorter number of path variables:
#GetMapping("/api/v1/data/{provider}/{subject}/{dataset}")
public List<List<String>> getEDXDatasetHead(
#PathVariable final String provider,
#PathVariable final String subject,
#PathVariable final String dataset,
#RequestParam(required = false, value = "date") final String date
)
...
#GetMapping("/api/v1/data/{provider}/{subject}")
public List<List<String>> getEDXDatasetHead(
#PathVariable final String provider,
#PathVariable final String subject,
#RequestParam(required = false, value = "date") final String date
)
...
#GetMapping("/api/v1/data/{provider}")
public List<List<String>> getEDXDatasetHead(
#PathVariable final String provider,
#RequestParam(required = false, value = "date") final String date
)
Note: spring doesn't have a particular limitation on the number of PathVariable that can be used in a method. But there are limitations on:
the size of the generated url (if too long not all browser handle it correctly)
the format of the url (check if you url encoded the path variables when you build the url on the client side)

Related

Avoid duplicate code in Controllers and service

I am using Spring framework.
I wrote some pretty long code to save some results.
So later it turned out in other controllers I will also need this code. Just with small differences for example returning some other strings.
So of course every controller will have its own mapping. So the parameters will be duplicate anyway.
But now for the code inside the mapping method.
I was thinking putting the code in the service of the original controller. Then the other controllers will call this service. The service of course will have plenty of parameters + the slight differences between the controllers. Or should I make like a general service, have good documentation there because of course the methods there will be general and later I should know what they were for.
#PostMapping("/testcase") public RedirectView saveResult(Model model, #ModelAttribute("testResultEntity") TestResultEntity testResultEntity, RedirectAttributes redirectAttributes , #RequestParam(required = false) String version , #RequestParam(required = false,defaultValue = "0") String page, #RequestParam(required = false) String operation, Authentication authentication,Locale locale)
{ // here comes long code, which will be used also in other controllers ;
}
If all the controller mappings have the same signature you can create a parent class with the common implementation.
Something like this:
public abstract class BaseAbstractController {
// specific logic per controller
abstract String specific();
public RedirectView save(Model model, #ModelAttribute("testResultEntity") TestResultEntity testResultEntity,
RedirectAttributes redirectAttributes, #RequestParam(required = false) String version,
#RequestParam(required = false, defaultValue = "0") String page, #RequestParam(required = false) String operation,
Authentication authentication, Locale locale) {
// here comes long code, which will be used also in other controllers ;
String specific = specific();
}
}
#Controller
public class TestController extends BaseAbstractController {
#Override
String specific() {
return "something"; // here goes your specific logic;
}
#PostMapping("/testcase")
public RedirectView saveResult(Model model, #ModelAttribute("testResultEntity") TestResultEntity testResultEntity,
RedirectAttributes redirectAttributes, #RequestParam(required = false) String version,
#RequestParam(required = false, defaultValue = "0") String page, #RequestParam(required = false) String operation,
Authentication authentication, Locale locale) {
return save(model, testResultEntity, redirectAttributes, version, page, operation, authentication, locale);
}
}

#Valid for long data type is not working for mandatory check

I have the below input class and when i trigger the api without 'interactionId' param in the input,
I expect validation error message "interactionId cannot be empty" but the validation passes through which i guess could be due to the fact that interactionId has a default value of 0.
Can someone pls. help to enforce this validation on the 'long' parameter when its not given in input?
with #NotEmpty for the customerId param, its working as expected. Using #NotEmpty for the long param "interactionId" is throwing a different error that #notempty cannot be used for long.
public class Input {
#NotEmpty(message = "customerId cannot be empty")
private String customerId;
#Valid
#NotNull(message = "interactionId cannot be empty")
private long interactionId;
// setters and getters
}
my controller class:
#RestController
public class Controller {
#PostMapping(value="/detailed-customer-transaction", produces =
MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public ResponseEntity<Object> detailTransactions(#Valid #RequestBody Input
params)
{
return new ResponseEntity<>(Dao.detailTransactions(params), HttpStatus.OK);
}
Above issues is resolved after changing to Long instead of long.
Query #2
I need another help. I have a String input param which takes date-time format in below format. Given its a string parameter, how can i validate for the pattern="yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
long should be Long, because long is a primary type in java, not an object, so Long is an object that can be checked whether it is null

Thymeleaf add multiple parameters to URL in one go

Given I have MVC endpoint with mapping:
#RequestMapping
public String eventHistory(final Model model,
#PageableDefault(sort = {"id"}, direction = DESC) final Pageable pageable,
final EventHistoryFilter eventHistoryFilter) {
...
model.addAttribute("eventHistoryFilter", eventHistoryFilter);
}
where EventHistoryFilter is:
final class EventHistoryFilter {
private Long eventId;
private String eventType;
private String eventDateFrom;
private String eventDateTo;
...
}
and in thymeleaf template I would like to construct URL with parameters, e.g.:
th:href="#{/event-history(page=${page.number-1},eventId=${eventHistoryFilter.eventId},eventType=${eventHistoryFilter.eventType},eventDateFrom=${eventHistoryFilter.eventDateFrom},eventDateTo=${eventHistoryFilter.eventDateTo})}"
How can I add multiple parameters into URL in one go?
I didn't find it here: https://www.thymeleaf.org/doc/articles/standardurlsyntax.html#adding-parameters
I'd like to avoid specifying each parameter one by one.
EDIT:
I tried to use https://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html#building-uris-to-controllers
String url = MvcUriComponentsBuilder.fromMappingName("EHE#eventHistory").arg(2, eventHistoryFilter).build();
but resolved URL doesn't contain any request parameters.
and thymeleaf counterpart:
th:href="${#mvc.url('EHE#eventHistory').arg(2,__${eventHistoryFilter}__).build()}"
fails during evaluation.

Invoking service with #RequestParam and #RequestBody using postman client

Was trying to invoke the service
http://IP:8080/PQRS/LMN/XYZ/runTest/scheduledautomation/1/XYZ
with below JSON String
[ {"paramName":"TEST_TARGET_IDENTIFIER","paramValue":"ETest"},{"paramName":"TEST_SOURCE_ENTRY_IDENTIFIER","paramValue":"com.pack.etest"}]
#ResponseStatus(value = HttpStatus.NO_CONTENT)
#RequestMapping(value = "/runTest/scheduledautomation/{runId}/{testEngine}", method = RequestMethod.POST)
public void runScheduledAutomatedTest(#RequestParam String cronExpresssion,
#RequestParam(required = false) #DateTimeFormat(iso = ISO.DATE_TIME) LocalDateTime endTime,
#PathVariable Integer runId,
#PathVariable TestEngine testEngine,
#RequestBody List<TestEngineParam> testEngineParams) throws Exception { //Some Code }
Response :
Required String parameter 'cronExpresssion' is not present
how to invoke mixed #RequestParam and #RequestBody services on postman client ?
I fear you want a little bit too much: RequestParam, RequestBody AND the whole thing as a REST query. At least two of the three things are mutually exclusive.
I think you could even get Postmaster to do this by modifying the called URL to:
http://IP:8080/PQRS/LMN/XYZ/runTest/scheduledautomation/1/XYZ?cronExpression=your-expression
Of course this would ruin your REST interface, but as I said: your handler method is a little bit "over-ambitious".

How to handle a request with multiple parameters on Spring-MVC

I am sending following request that need to be handled by Spring-MVC but it does not.
http://localhost:2001/MyProject/flights/find?origin=LAX&destination=WA&departure=&arrival=&return=false
Code
#Controller
#RequestMapping("/flights")
public class FlightController {
#RequestMapping(value = "/find?origin={origin}&destination={destination}&departure={departure}&arrival={arrival}&return={return}", method = RequestMethod.GET)
public String findFlight(#PathVariable String origin,
String destination, Date departure, Date arrival, boolean return) {
That is not the correct way (or place) to use #PathVariable. You need to use #RequestParam.
#Controller
#RequestMapping("/flights")
public class FlightController {
#RequestMapping("/find")
public String findFlight(#RequestParam String origin
, #RequestParam String destination
, #RequestParam(required = false) Date departure
, #RequestParam(required = false) Date arrival
, #RequestParam(defaultValue = "false", required = false, value = "return") Boolean ret) { ... }
}
Note that return is a keyword in Java so you cannot use it as a method parameter name.
You will also have to add a java.beans.PropertyEditor for reading the dates because the dates will (presumably) be in a specific format.
Try this, may be it works:
#RequestMapping("/find")
public String findFlight(#RequestParam("origin") String origin
, #RequestParam("destination") String destination,....

Resources