Using Object class as wrapper for input in spring boot RestController - java

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

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

Is there a spring for graphql alternative for netflix's #DGSDataloader and DGSData

Trying to migrate a Netflix DGS GraphQL and Neo4J project to now use Spring for GraphQL and Neo4J instead. Hit a roadblock when I wanted to avoid the N+1 problem.
Yes, there is an alternative to avoid the N+1 problem in Spring for GraphQL. It's the #BatchMapping annotation:
Suppose you have the following schema:
type Query {
artistas: [Artista]
}
type Artista {
id: ID
apellido: String
estilo: String
obras:[Obra]
}
type Obra{
artistaId: ID
titulo: String
imagen: String
}
And the following #QueryMapping:
#QueryMapping
Flux<Artista> artistas(){
return Flux.fromIterable(allArtistas);
}
Our Artista DTO contains a List of Obra we may sometimes want, so it can cause us the N+1 problem:
record Artista (Long id, String apellido, String estilo, List<Obra> obras){}
record Obra (Long artistaId, String titulo, String imagen){}
So if you add an additional mapping method annotated with #BatchMapping, you tell the GraphQL engine to fetch that data using a DataLoader under the hood and keeping it at hand for each DB roundtrip, for example.
#BatchMapping(typeName = "Artista")
Mono<Map<Artista, List<Obra>>> obras(List<Artista> artistas){
var artistasIds = artistas.stream()
.map(Artista::id)
.toList();
var todasLasObras = obtenerObras(artistasIds);
return todasLasObras.collectList()
.map(obras -> {
Map<Long, List<Obra>> obrasDeCadaArtistaId = obras.stream()
.collect(Collectors.groupingBy(Obra::artistaId));
return artistas.stream()
.collect(Collectors.toMap(
unArtista -> unArtista, //K, el Artista
unArtista -> obrasDeCadaArtistaId.get(Long.parseLong(unArtista.id().toString())))); //V, la lista de obras
});
}
private Flux<Obra> obtenerObras(List<Long> artistasIds) {
// ...your service-specific way of getting all the Obras from each artistaId...
}
If you throw some logs here and there you can check it only fetches the Obras once.
Hope it helps!

How to extract parameters from an object to show in parameters in documentation

I have the following API endpoint:
#ApiResponses(
value = {
#ApiResponse(code = 200, message = "OK",
responseHeaders = {
#ResponseHeader(name = "X-RateLimit-Limit", description = "The defined maximum number of requests available to the consumer for this API.", response = Integer.class),
#ResponseHeader(name = "X-RateLimit-Remaining", description = "The number of calls remaining before the limit is enforced and requests are bounced.", response = Integer.class),
#ResponseHeader(name = "X-RateLimit-Reset", description = "The time, in seconds, until the limit expires and another request will be allowed in. This header will only be present if the limit is being enforced.", response = Integer.class)
}
)
}
)
#ApiOperation(httpMethod = "GET", hidden = false, nickname = "Get Network Availability in JSON", value = "Get network availability for a product", response = AvailableToPromise.class, position = 1)
#RequestMapping(value = "/{product_id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> networkAvailabilityJsonResponse(
#RequestHeader HttpHeaders headers,
#PathVariable("product_id") String productId,
#Valid NetworkAvailabilityCmd cmd, //query params
BindingResult result)
throws Exception {}
}
Certain parameters, such as key are taken from the query and mapped into this object through Spring MVC.
However, in the parameters section of my endpoint in the swagger-ui, it's showing me a few odd things:
None of the variables that are in NetworkAvailabilityCmd show in this parameters list, and cmd itself shows as being located in the request body (it's actually located in the query). Is there a way to hide cmd and extract the params inside this object to show on the params list? I'd like the params list to look like this (with more params):
I'm able to do this if I use #ApiImplicitParams on the method endpoint, and write out each of the params. However, this NetworkAvailabilityCmd is used for many endpoints, and having the list of params on each endpoint is very messy. Being able to extract the variables from in the object would be far cleaner, and would prevent people from forgetting to add the entire list to new endpoints.
I imagine that it requires an annotation on NetworkAvailabilityCmd cmd, and potentially something on the variables in that class, but I can't seem to find what I'm looking for in the docs.
Thanks!
I found out that adding #ModelAttribute worked magically. This annotation is from Spring.

How to share information between Spring controller methods from GET and POST requests?

I'm new to Spring and I want to:
1) when an user visits localhost/admin/users I want the predefined options to apply
2) On localhost/admin/users I have some buttons that perform a POST with four parameters because my boss don't want me to use get (and I think is better to use POST, too)
3) I have a controller method adminUsersPost that manages the POST request, and I want that method to be able to make my browser to reload using the adminUsersGet method, but with the information sent in the POST request.
What I'm getting now in my browser is an alert with a webpage content in some weird encoding, I hope it is correct but I don't know.
#RequestMapping(value = "/admin/users", method = RequestMethod.GET)
public ModelAndView adminUsersGet(
Integer page,
Integer items,
String sorting,
String sortingDirection)
{
// predefined options
Integer pagina = 1;
Integer itemsPorPagina = 10;
String ordenacion = "idUsuario";
String dirOrdenacion = "asc";
// end of predefined options
// Code that I want for it to use POST params from the other method
ModelAndView mv = new ModelAndView("adminUsers");
return mv;
}
#RequestMapping(value = "/admin/users", method = RequestMethod.POST)
public ModelAndView adminUsersPost(
#RequestParam(value = "pagina") Integer pagina,
#RequestParam(value = "itemsPorPagina") Integer itemsPorPagina,
#RequestParam(value = "ordenacion") String ordenacion,
#RequestParam(value = "dirOrdenacion") String dirOrdenacion)
{
// Here I try to pass the POST parameters to the GET method for reloading
// the webpage with the new content
return adminUsersGet(pagina, itemsPorPagina, ordenacion, dirOrdenacion);
}
The pattern POST params-->GET same parameters is a common one. What you need is RedirectAttributes which will store your parameters into the session and redirect to your GET method. Once the GET is hit spring will automatically remove all attributes from the session, thus none of the POST parameters will be displayed in the browser url in the GET method. Have a look here for a complete example and adjust it for your needs.

Spring RestTemplate: sending array / list of String in GET request

I'm trying to send a array / list of String to my REST server through Spring RestTemplate.
This is on my android side:
private List<String> articleids = new ArrayList<>();
articleids.add("563e5aeb0eab252dd4368ab7");
articleids.add("563f2dbd9bb0152bb0ea058e");
final String url = "https://10.0.3.2:5000/getsubscribedarticles";
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url)
.queryParam("articleids", articleids);
java.net.URI builtUrl = builder.build().encode().toUri();
Log.e("builtUrl", builtUrl.toString());
The builtUrl is: https://10.0.3.2:5000/getsubscribedarticles?articleids=%5B563e5aeb0eab252dd4368ab7,%20563f2dbd9bb0152bb0ea058e%5D
On the server side:
#RequestMapping(value = "/getsubscribedarticles", method = RequestMethod.GET)
public List<Posts> getSubscribedPostFeed(#RequestParam("articleids") List<String> articleids){
for (String articleid : articleids {
logger.info(" articleid : " + articleid);
}
}
The server logs:
.13:11:35.370 [http-nio-8443-exec-5] INFO c.f.s.i.ServiceGatewayImpl
- articleid : [563e5aeb0eab252dd4368ab7
.13:11:35.370 [http-nio-8443-exec-5] INFO c.f.s.i.ServiceGatewayImpl
- articleid : 563f2dbd9bb0152bb0ea058e]
Which I can see is wrong as the list should not have a '[' on the first item and a ']' on the last item.
I have read this thread How to pass List or String array to getForObject with Spring RestTemplate but it does not actually answer the question.
The selected answer issues out a POST request, but I want to do a GET request , also it requires an additional object to work to hold the list and I would prefer to not create extra objects if I can do it with Spring RestTemplate natively.
Using Java 8, this worked for me :
UriComponentsBuilder builder = fromHttpUrl(url);
builder.queryParam("articleids", String.join(",", articleids));
URI uri = builder.build().encode().toUri();
It forms the URL like:
https://10.0.3.2:5000/getsubscribedarticles?articleids=123,456,789
I would expect that the correct working url is something like:
https://10.0.3.2:5000/getsubscribedarticles?articleids[]=123&articleids[]=456&articleids[]=789
After a quick look at the code of public UriComponentsBuilder queryParam(String name, Object... values), I would solve it by using UriComponentsBuilder this way:
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url)
.queryParam("articleids[]", articleids.toArray(new String[0]));
It is important that, the second parameter is an array but not an Object/Collection!
You did everything correct. You just need to call it without the [].
Just invoke it with .../getsubscribedarticles/articleids=foo,bar,42
I tested this with Spring Boot 1.2.6 and it works like this.
Thanks to dOx for his suggestion - I managed to solve this with the PathVariable - i set the list in my url for android:
final String url = "https://10.0.3.2:5000/getsubscribedarticles/"+new ArrayList<>(articleids);
For my rest server:
#RequestMapping(value = "/getsubscribedarticles/[{articleids}]", method = RequestMethod.GET)
public List<Posts> getSubscribedPostFeed(#PathVariable String[] articleids){
}

Categories

Resources