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());
Related
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
In Spring Boot 1.5.4 I have a request mapping like this:
#RequestMapping(value = "/graph/{graphId}/details/{iri:.+}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#Timed
public JSONObject getGraph(#PathVariable Long graphId,
#PathVariable String iri) {
log.debug("Details called for graph ID {} for IRI {}", graphId, iri);
return detailsService.getDetails(graphId, iri);
}
Accessing
http://localhost:9000/api/v1/graph/2/details/http%3Anthnth33
works fine and the server maps the request correctly and the code returns the expected result
But accessing
http://localhost:9000/api/v1/graph/2/details/http%3A%2F%2Fserverurl.net%2Fv1%2Fus%2Fh.schumacher%408tsch.net%2Fn%2FLouSchumacher
gives a bad server request (Failed to load resource: the server responded with a status of 400 (Bad Request)). The request mapping to the end point isn't even done in that case.
Obviously the slash '/' encoded as %2F (using encodeURIComponent()) causes trouble. Why? What am I missing? How should uri parameter then be encoded?
The question is not only about how to extract PathVariables but more on how to force String to recognize the correct mapping.
The issue with your example is how Spring is doing path matching. The URL you have provided as example
http://localhost:9000/api/v1/graph/2/details/http%3A%2F%2Fserverurl.net%2Fv1%2Fus%2Fh.schumacher%408tsch.net%2Fn%2FLouSchumacher
will be decoded into by container
http://localhost:9000/api/v1/graph/2/details/http://serverurl.net/v1/us/h.schumacher#8tsch.net/n/LouSchumacher
before processing by Spring matcher. This makes matche think that this only http: corresponds {iri:.+} and as later goes / so it is some longer path you don't have a mapping for.
The approach described here should work for you: Spring 3 RequestMapping: Get path value
#RequestMapping(value = "/graph/{graphId}/details/**",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#Timed
public JSONObject getGraph(#PathVariable Long graphId,
HttpServletRequest request) {
String iri = (String) request.getAttribute(
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
log.debug("Details called for graph ID {} for IRI {}", graphId, iri);
return detailsService.getDetails(graphId, iri);
}
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).
I have a Spring controller with two parameter long and String:
#RequestMapping(value = "/webpage")
#Controller
public class WebpageContentController {
//...
#RequestMapping(value = "{webpageId}/{webpageAddress}", method = RequestMethod.GET)
public String contentWebpageById(#PathVariable long webpageId, #PathVariable String webpageAddress) {
System.out.println("webpageId=" + webpageId);
System.out.println("webpageAddress=" + webpageAddress);
//...
}
//...
If I invoke it like this:
http://localhost:8080/webarch/webpage/1/blahblah
All is fine:
webpageId=1
webpageAddress=blahblah
But If I pass String parameter with slash (in this case URL address):
http://localhost:8080/webarch/webpage/1/https://en.wikipedia.org/wiki/Main_Page
I get an error:
org.springframework.web.servlet.PageNotFound.noHandlerFound No mapping found for HTTP request with URI [/webarch/webpage/1/https://en.wikipedia.org/wiki/Main_Page] in DispatcherServlet with name 'appServlet'
How pass such parameter?
Well the error is caused by springs controllers mapping, when Spring sees url like
http://localhost:8080/webarch/webpage/1/https://en.wikipedia.org/wiki/Main_Page
It doesn't 'know' that the 'https://en.wikipedia.org/wiki/Main_Page' should be mapped as parameter to "{webpageId}/{webpageAddress}" mapping since every slash is interpreted as a deeper controler method mapping. It looks for controller method mapping like (webpage/1/http:{anotherMapping}/wiki{anotherMapping}/Main_Page{anotherMapping}) wich this kind of mapping is obviously not handled by "{webpageId}/{webpageAddress}"
EDIT
According to your comment you can try something like this
#RequestMapping(value = "/{webpageId}/**", method = RequestMethod.GET)
public String contentWebpageById(HttpServletRequest request) {
String pattern = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
String extractedPathParam = pathMatcher.extractPathWithinPattern(pattern, request.getServletPath());
extractedPathParam = extractedPathParam.replace("http:/", "http://");
extractedPathParam = extractedPathParam.replace("https:/", "https://");
//do whatever you want with parsed string..
}
Using spring 4.2.1
SomeParsing should use some Regular Expression to extract only the URL 'variable'
Just encode all special characters in the URL.
https://en.wikipedia.org/wiki/Main_Page
becomes this:
https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FMain_Page
and you can pass it as URL parameter without any problems. Decoding is done automatically, so if you access the parameter as variable in your controller, it contains the URL already decoded and you can use it without any converting needed.
More information about URL encoding: https://en.wikipedia.org/wiki/Percent-encoding
I am working to cater REST url of three types:
url/detail/3 (integer only)
url/detail/hello (String only)
url/detail/3/1d
For URL 1 and 3 I am using method1 and for URL 2 method2 is used.
Problem 1: All requests type of 1 and 2 matches method 2 only.Though I've specified Integer pattern in method 1 for queries having integer specifically.
Problem 2: To use an optional param (like in 3) I am using method1 because jersey doesn't provide any option for optional param.But url types of url/detail/3/1d is never matched as specified in method 1.
Please help me understand what I am doing wrong as I am newbie to jersey.
#GET
#Path("/detail/{id: \\d+}/{time-period:(/time-period/[^/]+?)?}")
#Produces({ MediaType.APPLICATION_JSON })
#Consumes(MediaType.APPLICATION_JSON)
public JResponse method1(
#Context HttpHeaders headers,
#PathParam("id") String id,
#PathParam("time-period") String timePeriod) {
if(timePeriod == null || timePeriod.equals(""))
{
//code
}
else
//code
}
#GET
#Path("/detail/{name}")
#Produces({ MediaType.APPLICATION_JSON })
#Consumes(MediaType.APPLICATION_JSON)
public JResponse method2(
#Context HttpHeaders headers, #PathParam("name") String name) {
//code
}
Maybe a missing whitespace leads to the problem. See Optional #PathParam in Jax-RS
You can define default values for parameters with #DefaultValue("1000")
Instead of complicated regexps you should probably use subresource.
#Path("detail/{id}{time-perioid:(/[^/]+?)?}")
http://x.y.z:4080/analytics/internal/detail/kala
2014-01-17 07:35:50,509 [http-nio-4080-exec-8] INFO xxx - id: kala
2014-01-17 07:35:50,510 [http-nio-4080-exec-8] INFO xxx - time-period:
and
http://x.y.z:4080/analytics/internal/detail/kala/123
2014-01-17 07:36:01,644 [http-nio-4080-exec-9] INFO xxx - id: kala
2014-01-17 07:36:01,645 [http-nio-4080-exec-9] INFO xxx - time-period: /123
If id can be string or integer I would go validating it inside handler.