SpringBoot Controller mapping to incorrect method - java

I have below 2 GET mappings in my controller:
1. #GetMapping("/department/{deptId}/employee/{employeeId}")
public String func1(#PathVariable(value = "deptId", required = true) String deptId,
#PathVariable(value = "employeeId", required = true) String employeeId) { ... }
2. #GetMapping("/department/{deptId}/employee/{employeeId}/workLogs")
public String func2(#PathVariable(value = "deptId", required = true) String deptId,
#PathVariable(value = "employeeId", required = true) String employeeId) { ... }
When I Fire the API as:
GET http://localhost:8080/department/102/employee//workLogs --> Keeping employeeId as blank, this call gets mapped to the first GetMapping (func1) and employeeId is calculated as employeeId = "workLogs".
Hence, There is no exception thrown for missing path variable which was marked as required and call completed with 200 OK.
How to resolve this, so that it maps correctly to func2, and throws an exception for missing required path variable.

When you make a request
http://localhost:8080/department/102/employee/workLogs
This will be interpreted as workLogs being provided as the employeeId.
There's a couple ways to solve the problem.
In func1, throw an exception if employeeId.equals("workLogs")
set employeeId as an Int or Long, so that an exception will be thrown by default when workLogs is attempted to be parsed as an employeeId
But actually, calling http://localhost:8080/department/102/employee//workLogs with the double slash (//) should result in a 404 error. Try using version 5.3.15 of Spring if this isn't the case.

Related

Java Spring Boot: Swagger GET List in the Request

I am trying to setup a GET request in Java Spring Boot Swagger, with a list of ProductIds in the request. How can I edit the code below for this?
#GET
#Path("/product/{customerId}/{productIds}")
#ApiOperation(
value = "Get Products",
response = ProductResponse.class,
responseContainer = "List"
)
List<ProductResponse> getProductData(
#ApiParam(value = "customerId", required = true) #PathParam("customerId") long customerId,
#ApiParam(value = "productIds", required = true) #PathParam("productIds") List<Long> productIds
);
Result: with CustomerId 7 and ProductIds (2,3)
404 Not Found
http://localhost:50111/product-domain/api/product/7/2%2C3
Update: if I use RequestParam for ProductIds, how would I input this in swagger body? Note: any solution will work, (don't necessarily need to use RequestParam)
#RequestParam(value="productIds", required=true) List<Long> productIds
In my opinion you should not use PathVariable but RequestParam (preferably RequestBody) in this case.
In case of using RequestParam it is should looks like:
#Path("/product/{customerId}")
List<ProductResponse> getProductData(
#ApiParam(value = "customerId", required = true) #PathParam("customerId") long customerId,
#ApiParam(value = "productIds", required = true) #RequestParam("productIds") List<Long> productIds
);
than your url will look like: http://localhost:50111/product-domain/api/product/7?productIds=2,3
In case of using RequestBody it is should looks like:
#Path("/product/{customerId}")
List<ProductResponse> getProductData(
#ApiParam(value = "customerId", required = true) #PathParam("customerId") long customerId,
#ApiParam(value = "productIds", required = true) #RequestBody("productIds") List<Long> productIds
);
than your url will look like: http://localhost:50111/product-domain/api/product/7
and your http request body should contains: [2, 3]
Why I advise against using #PathParam in this case?
Url length has length limit (around 2048 characters) - so if You try pass long list in future it is can be a problem
Url needs to "normalize"/"escape" special characters, what makes url less readable for human, which is the essence of using PathParam
BTW:
Consider using PathVariable instead of PathParam - because PathVariable is from Spring, but PathParam is from JAX-RS and I assume you want to use Spring Boot

Using Object class as wrapper for input in spring boot RestController

I am bit new to spring boot and I am trying to design a search on user history which will provide 3 attributes to search user history {userId, searchKey, SearchValue}.
The search value datatype may differ based on search.
E.g
Userid=100, SearchKey=userAddress, searchValue='10 Downing Street'
Userid=100, SearchKey=external, searchValue=true
Userid=100, SearchKey=companyId, searchValue=25
I am trying to design a rest endpoint as below. This endpoint will integrate with react front end.
#GetMapping(value = "/searchUserHistoryByKeyValue")
public ResponseEntity<Object> searchUserHistoryByKeyValue(
#RequestParam(value = "userId") int userId,
#RequestParam(value = "searchKey") String searchKey,
#RequestBody Object searchValue) {
List<org.json.simple.JSONObject> entities =
userHistoryService.searchUserHisotryByKeyValue(userId, searchKey, searchValue);
return new ResponseEntity<>(entities, HttpStatus.OK);
}
I have implemented a dynamodb search on userhistory object which takes input as generic searchValue object as search filter as below.
Dynamo DB Querying - https://www.tutorialspoint.com/dynamodb/dynamodb_querying.htm
public List<JSONObject> searchUserHistoryByKeyValue(
int userId, String searchKey, Object searchValue) throws DataAccessException {
Table table = dynamoDB.getTable(userHistoryTable.getName());
Map<String, String> expressionAttributeNames =
DEFAULT_USER_FILTERS.stream()
.collect(
Collectors.toMap(attrib -> attrib, attrib -> attrib.substring(1), (a, b) -> b));
Optional<String> projectionExpression =
createProjectionExpression(
Collections.singletonList(searchKey), expressionAttributeNames);
Optional<String> filterProjectionExpression =
buildCustomProjectionExpression(
Collections.singletonList(searchKey), expressionAttributeNames);
QuerySpec querySpec =
new QuerySpec()
.withProjectionExpression(projectionExpression.orElse(StringUtils.EMPTY))
.withKeyConditionExpression("#userId = :userId")
.withFilterExpression(
String.format(
"%s = :searchValue",
filterProjectionExpression.orElseThrow(
() -> new IllegalArgumentException("Invalid Search Attributes"))))
.withNameMap(expressionAttributeNames)
.withValueMap(Map.of(":userId", userId, ":searchValue", searchValue))
.withScanIndexForward(false);
When I am trying use swagger or postman to test this endpoint , I am not able to pass in
#RequestBody Object searchValue . it just shows as empty braces - {}
Also it shows below error as -
'TypeError: Failed to execute 'fetch' on 'Window': Request with
GET/HEAD method cannot have body. '
I am not able to make this work? Appreciate your insights on this.
It's HTTP protocol.
You cannot pass any body object with the Get method. You have to use Post or Put method for using a body in HTTP request.
#RequestBody not for single value it is intended for your custom object that is used with POST or PUT but in you case you can #RequestParam also if #RequestParam take attribute required with boolean vlue which tell your endpoint caller which params is optional if you set it False and which is required if you set it True

how to accept a future date in a param request?

I would like that in my controller the update service would only accept dates after the current one, I tried to use the future annotation but it doesn't work ... could you give me a suggestion?
in particular, I would like if the user entered the date 1990-04-01T12: 08: 56Z the service would return 400 because the entered date is not later than the current one, I thought the #Future annotation worked in this sense instead I was wrong and still allows the execution of the service
#PatchMapping(path = "/{id}", produces = "application/json")
public ResponseEntity<Employee> update(#PathVariable(value = "id") Integer id,
#RequestParam(value = "afterdate") #Future #DateTimeFormat(pattern =" yyyy-MM-dd'T'HH:mm:ss'Z'") Date afterdate) { ....

JAX-RS, how to replace a queryParam value

So I have to create a filter that adds/formats date strings.
My implementation was to create a ContainerRequestFilter and perform formatting there; then add this custom filter through #NameBinding.
My problem seems to be that #NameBinding is ignored when used with #PreMatching, thus not being able to work since I also use reflection to extract properties from my filter/annotation.
So after performing formatting in filter, the idea is to use:
uriBuilder.replaceQueryParam(startDateQueryParamName, formattedString);
but even if I add a hardcoded value, the value is still the original.
Say I make a request: .../api/x?startDate=1234-01-01T00:00:00
And I hardcode in filter:
`uriBuilder.replaceQueryParam(startDateQueryParamName, "2020-05-05T00:00:00");`
I still get 1234-01-01T00:00:00 in resource method:
#GET
#Path("/t1")
#Produces(MediaType.TEXT_PLAIN)
#StartEndDateFilter(required = true)
public String testLocalDateTime(#QueryParam("startDate") LocalDateTime startDate, #QueryParam("endDate") LocalDateTime endDate, #Context UriInfo urinfo) {
MultivaluedMap<String, String> m = urinfo.getQueryParameters();
String d = startDate == null ? "nothin " : startDate.toString();
String e = endDate == null ? "nothin " : endDate.toString();
return String.format("start: %s \nend: %s", d, e);
}
So, I thought maybe using #PreMatching would help but, as I mentioned, this shows a warning:
Warning: #PreMatching provider, class com.api.DateRangeFilter, also
annotated with a name binding annotation. Name binding will be
ignored.
And on top of that, when I call requestContext.setRequestUri(uriBuilder.build()); I get the following error when I call the endpoint:
Warning: StandardWrapperValve[com.api.Ap]: Servlet.service() for
servlet com.api.Ap threw exception java.lang.IllegalStateException:
Method could be called only in pre-matching request filter. at
org.glassfish.jersey.server.ContainerRequest.setRequestUri(ContainerRequest.java:411)
at com.api.DateRangeFilter.filter(DateRangeFilter.java:153)
line 153 is:
requestContext.setRequestUri(uriBuilder.build());

In Spring MVC, can't bind an input to a date field with a Jackson #JsonFormat

I have an application using Spring MVC that interacts with a REST service. The UI has a typical form input using JSP.
There is an object that I wish to allow the user to modify and persist that includes a date field:
public class TheObject {
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd", timezone = "PST")
private Date myDate;
.
.
.
}
And on the UI this is bound to an input:
<form:input path="myDate"/>
So, in my controller when I post the form and I've entered a proper "yyyy-MM-dd" string in that input box I get null for the field and a binding error. Controller method looks like this
#RequestMapping(value = "thePath", method = RequestMethod.POST)
public String postMyForm( #Valid #ModelAttribute final theObject backingModel, final BindingResult result, final Model model,
final HttpServletRequest request) throws Exception {
//Breakpoint here to check the binding
}
If I look at the BindingResult there I see an error saying the following:
Field error in object 'backingModel' on field 'theDate': rejected value [2016-07-07]; codes [typeMismatch.backingModel.theDate,typeMismatch.theDate,typeMismatch.java.util.Date,typeMismatch];
arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [backingModel.theDate,theDate];
arguments []; default message [theDate]];
default message [Failed to convert property value of type [java.lang.String] to required type [java.util.Date] for property 'theDate';
nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [#com.fasterxml.jackson.annotation.JsonFormat java.util.Date] for value '2016-07-07'; nested exception is java.lang.IllegalArgumentException]
If I take out the #Valid I get an exception with the same message.
How should I be able to bind this?
If I replace the annotation with #DateTimeFormat(pattern = "yyyy-MM-dd") then binding works fine. But the object needs that Jackson annotation.
So after posting all that I realized I can just add both annotations and it works
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd", timezone = "PST")
#DateTimeFormat(pattern = "yyyy-MM-dd")
private Date myDate;
So I'll post this as the answer in case anyone else comes across this (unless someone thinks what I'm describing above is really bad practice or anything).

Categories

Resources