CompletableFuture: how to combine two asynchronous requests into one response - java

The first request is sent asynchronously, then the id value is taken from the response to the first request and used in the second asynchronous request. And then both responses to request 1 and request 2 are combined. The example below works, but is not asynchronous because .get() is used. Is there a way to do this asynchronously?
The process in short - everything should happen asynchronously:
Send POST request 1
Use the id value from response 1 for request 2
Send POST request 2
Combine response 1 and response 2 to the final response of the REST controller
This body is sent to the REST controller endpoint "/combine" via POST method:
{
"userid": 1,
"id": 2,
"title": "3",
"body": "4" }
#RestController
public class CombinationController {
#Autowired
CombinationService combinationService;
#PostMapping("/combine")
public CompletableFuture<CombinationBothResponses> combine(#RequestBody RequestBodyOne requestBodyOne) {
return combinationService.combine(requestBodyOne);
}
}
#Service
public class CombinationService {
private final Jsonb jsonb = JsonbBuilder.create();
private final HttpClient httpClient = HttpClient.newBuilder().build();
public CompletableFuture<CombinationBothResponses> combine(RequestBodyOne requestBodyOne) {
// 1. Send POST request 1
HttpRequest httpRequestOne =
HttpRequest.newBuilder(URI.create("https://jsonplaceholder.typicode.com/posts"))
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.POST(HttpRequest.BodyPublishers.ofString(jsonb.toJson(requestBodyOne)))
.build();
return httpClient
.sendAsync(httpRequestOne, HttpResponse.BodyHandlers.ofString())
.thenApply(
httpResponse -> {
if (HttpStatus.valueOf(httpResponse.statusCode()).is2xxSuccessful()) {
CombinationBothResponses combinationBothResponses =
jsonb.fromJson(httpResponse.body(), CombinationBothResponses.class);
// 2. Use one value from response 1 for request 2
int valueToBeUsedInRequestBody2 = combinationBothResponses.getId();
// 3. Send POST request 2
CompletableFuture<CombinationBothResponses> completableFuture2 =
sendSecondPostRequest(valueToBeUsedInRequestBody2);
// 4. Combine response 1 and response 2 to the final response of REST controller
try {
CombinationBothResponses responseBodyRequestTwo =
completableFuture2.get(); // Not asynchronous
combinationBothResponses.setSuccess(responseBodyRequestTwo.getSuccess());
return combinationBothResponses;
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
throw new RuntimeException();
});
}
private CompletableFuture<CombinationBothResponses> sendSecondPostRequest(
int valueToBeUsedInRequestBody2) {
RequestBodyTwo requestBodyTwo = new RequestBodyTwo(valueToBeUsedInRequestBody2, "request 2");
HttpRequest httpRequest =
HttpRequest.newBuilder(URI.create("https://reqbin.com/echo/post/json"))
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.POST(HttpRequest.BodyPublishers.ofString(jsonb.toJson(requestBodyTwo)))
.build();
return httpClient
.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString())
.thenApply(
httpResponse -> {
if (HttpStatus.valueOf(httpResponse.statusCode()).is2xxSuccessful()) {
CombinationBothResponses responseBodyRequestTwo =
jsonb.fromJson(httpResponse.body(), CombinationBothResponses.class);
return responseBodyRequestTwo;
}
throw new RuntimeException();
});
}
}
#Data
#NoArgsConstructor
public class RequestBodyOne {
private int userId;
private int id;
private String title;
private String body;
}
#Data
#AllArgsConstructor
#NoArgsConstructor
public class RequestBodyTwo {
private int id;
private String key;
}
#Data
#NoArgsConstructor
public class CombinationBothResponses {
private int userId;
private int id;
private String title;
private String body;
private String success;
}
Response to request 1:
{ "userId": 0, "id": 101, "title": "3", "body": "4" }
Response to request 2:
{"success":"true"}
Combined responses; response of REST controller:
{
"userId": 0,
"id": 101,
"title": "3",
"body": "4",
"success": "true" }

return httpClient
.sendAsync(httpRequestOne, HttpResponse.BodyHandlers.ofString())
.thenCompose(httpResponse -> {
if (HttpStatus.valueOf(httpResponse.statusCode()).is2xxSuccessful()) {
final CombinationBothResponses combinationBothResponses = jsonb.fromJson(httpResponse.body(), CombinationBothResponses.class);
// 2. Use one value from response 1 for request 2
int valueToBeUsedInRequestBody2 = combinationBothResponses.getId();
// 3. Send POST request 2
return sendSecondPostRequest(valueToBeUsedInRequestBody2)
.thenApply(responseBodyRequestTwo -> {
// 4. Combine response 1 and response 2 to the final response of REST controller
combinationBothResponses.setSuccess(responseBodyRequestTwo.getSuccess());
return combinationBothResponses;
});
}
return CompletableFuture.failedFuture(new RuntimeException());
});

Related

Why does the string returned as "4-5 u043Bu0435u0442 103-112 u0441u043C"?

I am developing backend which collect data from different sources (REST APIs) and return summarized data.
So I requesting data from one of APIs. Response from that API is (all Cyrillic characters looks good) described as #Data class ProductSku {private string label; private boolean isInStock;}:
[
{
//... omit lot of unused fields
"label": "1 год 73-75 см",
"isInStock": true
},
{
//... omit lot of unused fields
"label": "2 года 82-88 см",
"isInStock": true
}
]
Then I allow to request it among other datas from my backend endpoint:
#GetMapping(path = "products/{prodId}", produces = {"application/json; charset=UTF-8","*/*;charset=UTF-8"})
public ProductEntity getProduct(#PathVariable("prodId") int prodId) {
return catalogService.getProduct(prodId);
}
class CatalogService {
public ProductEntity getProduct(int prodId) {
// ...
// ommited fill up result from other APIs
List<ProductData> productSkus = stockClient.getProductSkus(prodId);
productEntity.addSkus(productSkus);
return productEntity;
}
}
class StockClient {
public List<ProductSku> getProductSkus(int prodId) {
// using restTemplate
// querying by param productId
ProductData[] productsData;
try {
var headers = new HttpHeaders();
headers.set(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8");
headers.set(HttpHeaders.PRAGMA, "no-cache");
headers.set(HttpHeaders.CACHE_CONTROL, "no-cache");
var uri = UriComponentsBuilder.fromHttpUrl(apiUrl).queryParam("productId", prodId).build().toUri();
var requestEntity = RequestEntity.get(uri).headers(headers).build();
productsData = restTemplate.exchange(requestEntity, ProductData[].class).getBody();
} catch (...) {
//...
}
// convert to list here
return new Arrays.asList(productData);
}
}
// ...
So it respond me as:
{
// ...
"skus": [
{
"size": "1 u0433u043Eu0434 73-75 u0441u043C",
"inStock": true
},
{
"size": "2 u0433u043Eu0434u0430 82-88 u0441u043C",
"inStock": true
}
]
// ...
}
As you can see instead of "2 года 82-88 см" i get "2 u0433u043Eu0434u0430 82-88 u0441u043C"
Cyrillic symbols from other APIs appears normally.
I am hope for your help

Get both Mapping and Pain Json text in Spring Boot Restful webService

i am trying to parse json body request coming in for a post request using Spring Boot. I would like to map the body to fields on vehicle class and also to store plain json body to some variable as well for future use. But i am always getting stream closed exception when trying to access plain json body. Can someone help me out on this. Thanks In Advance
Code
#RequestMapping(value = "/GetDriverDetails", method = RequestMethod.POST)
public ResponseEntity<Vehicle> GetVehicleDetails(#RequestBody Vehicle vehicle, HttpServletRequest request) {
System.out.println(vehicle);
String json;
if ("POST".equalsIgnoreCase(request.getMethod()))
{
try {
ContentCachingRequestWrapper request1 = new ContentCachingRequestWrapper(request);
String collect = request1.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
System.out.println(collect);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return new ResponseEntity<Vehicle>(HttpStatus.OK);
}
Json request Body
{
"vehicleName": "Brio",
"vehicleModel": "fisrtClass",
"drivers": [
{
"name": "rej",
"licenseNumber": "KLLicense1"
},
{
"name": "Dan",
"licenseNumber": "KLLicense2"
},
{
"name": "bala",
"licenseNumber": "KLLicense3"
},
{
"name": "vijay",
"licenseNumber": "KLLicense4"
},
{
"name": "aravind",
"licenseNumber": "KLLicense5"
},
{
"name": "sathya",
"licenseNumber": "KLLicense6"
}
]
}
Exception
java.io.IOException: Stream closed
at org.apache.catalina.connector.InputBuffer.read(InputBuffer.java:359) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
at org.apache.catalina.connector.CoyoteInputStream.read(CoyoteInputStream.java:132) ~[tomcat-embed-core-9.0.38.jar:9.0.38]
at org.springframework.web.util.ContentCachingRequestWrapper$ContentCachingInputStream.read(ContentCachingRequestWrapper.java:254) ~[spring-web-5.2.9.RELEASE.jar:5.2.9.RELEASE]
at java.base/sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:297) ~[na:na]
at java.base/sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:339) ~[na:na]
at java.base/sun.nio.cs.StreamDecoder.read(StreamDecoder.java:188) ~[na:na]
at java.base/java.io.InputStreamReader.read(InputStreamReader.java:181) ~[na:na]
Can you try following code:
The solution to your main problem, since you are using #RequestBody, contents are already read and mapped to pojo class hence stream is utlized and closed in this case you do not want to use #RequestBody at all. Please find my implementation below:
#PostMapping(path = "update-vehicle-details", consumes = MediaType.ALL_VALUE)
public VehicleDriver updateVehicleDetails(HttpServletRequest request) throws IOException {
ContentCachingRequestWrapper request1 = new ContentCachingRequestWrapper(request);
String collect = request1.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
System.out.println(collect);
final VehicleDriver vehicleDriver = new ObjectMapper().readValue(collect, VehicleDriver.class);
return vehicleDriver;
}
Otherwise, use a simple approach, read the value from application json content type parses in requestbody and converts that body to string and return the same result
#RestController
public static class TestController {
#PostMapping(path = "update-vehicle-details", consumes = MediaType.APPLICATION_JSON_VALUE)
public String updateVehicleDetails(#RequestBody VehicleDriver vehicleDriver) throws JsonProcessingException {
final StringBuilder stringBuilder = new StringBuilder(vehicleDriver.vehicleName);
List<String> driverDetails = Optional.ofNullable(
vehicleDriver.drivers)
.map(Collection::stream)
.orElse(Stream.empty())
.map(d -> "name=: " + d.name + ", license number:" + d.licenseNumber)
.collect(Collectors.toList());
stringBuilder.append("\n");
stringBuilder.append(driverDetails);
String stringRepresentationOfBody = new ObjectMapper().writeValueAsString(vehicleDriver);
// return stringBuilder.toString();
return stringRepresentationOfBody;
}
}
public static class VehicleDriver {
public String vehicleName;
public String vehicleModel;
public List<Driver> drivers;
}
public static class Driver {
public String name;
public String licenseNumber;
}
Try using Object Mapper for converting your vehicle object to json string and in that case you would not be needing request in method argument.
And you are using post request method then if condition is not needed.

Spring ResponsEntity body contains extra json with timestamp, status and more

We have an REST endpoint that will add a new empty ingredient to an existing meal:
#RequestMapping(value = "/add", method = RequestMethod.PUT, consumes = "application/json;charset=UTF-8")
public ResponseEntity<Object> add(#RequestBody final Meal meal) throws URISyntaxException
{
Optional<Meal> optionalMeal = mealRepository.findById(meal.getId());
if (!optionalMeal.isPresent()) {
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(MessageUtil.parse(MSG_404_MEAL, meal.getId() + ""));
}
Ingredient ingredient = new Ingredient();
ingredient.setMeal(optionalMeal.get());
ingredientRepository.saveAndFlush(ingredient);
ResponseEntity re = ResponseEntity
.created(RequestUtil.getResourceURI(ingredient.getId()))
.body(ingredient);
return re;
}
Ingredient is an entity class with some fields:
public class Ingredient implements Serializable
{
#Id
private Integer id;
private Meal meal;
private Grocery grocery;
private Float amount;
...
}
RequestUtil takes care of creating the URI where the newly created resource is to be found:
public class RequestUtil
{
public static URI getResourceURI(int id) throws URISyntaxException
{
final String url = RequestUtil.getCurrentRequest().getRequestURL().toString();
final String req = RequestUtil.omitLast(url);
return new URI(req + "get/" + id);
}
public static HttpServletRequest getCurrentRequest()
{
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
return ((ServletRequestAttributes) requestAttributes).getRequest();
}
public static String omitLast(final String url) {
return url.substring(0, url.lastIndexOf("/") + 1);
}
}
The http status code and resource URI end up correctly in the response headers, but the body contains two JSONs:
{
"id": 407,
"meal": {
"id": 99,
"name": "New Meal",
"active": true
},
"grocery": null,
"amount": null,
"bought": false
} {
"timestamp": "2018-08-29T19:25:31.466+0000",
"status": 201,
"error": "Created",
"message": "No message available",
"path": "/ingredient/add"
}
Our javascript code does not expect this extra data and fails with
SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data at line 1 column 114 of the JSON data
Using a debugger, we can see that by the time the code reaches the return statement in add(), the ResponseEntity does not contain this extra data. Can someone explain where it comes from, and how we stop it from polluting the response?
Thanks for any help!

How I get RESTful response correctly?

I am consuming a restful with spring but I do not achieve get all data successfully.
RESTful response detail:
{
"errorCode": "0",
"errorMessage": "OK",
"transactionUUID": "2e48d6e7-f1fb-4271-b282-d74d3df1ef23",
"data":{
"price": "123",
"quantity" : "1",
"productname" : "Anystuff"}
}
My classes:
public class ResponseRest{
private String errorCode;
private String errorMessage;
private String transactionUUID;
private ResponseDetail data;
//get and set methods
}
public class ResponseDetail {
private String price;
private String quantity;
private String productname;
//get and set methods
}
Piece of my method in my controller class:
HttpHeader headers;
//headers.set....
String jsonParam = "{\"transactionToken\":\""+transactionToken+"\","
+ "\"sessionToken\":\""+sessionToken+"\"}";
RestTemplate restTemplate = new RestTemplate();
HttpEntity<String> entity = new HttpEntity<String>(jsonParam, headers);
response = restTemplate.exchange(URL_API_AUTH, HttpMethod.POST, entity, ResponseRest.class);
When I got result by console, I only get the properties from the ResponseRest class but not their detail from class ResponseDetail, console result:
System.out.println("-> Result - status ("+ response.getStatusCode() + ") has body with: " + (response.getBody().toString()));
Result - status (200) has body with: ResponseRest[errorCode="0", errorMessage="OK",transactionUUID="2e48d6e7-f1fb-4271-b282-d74d3df1ef23", data[price=null,quantity=null,productname=null]]
What I doing wrong?
Thanks a lot for their time.

415 Unsupported Media Type (JAVA + Angular + RestFULL JSON)

I hava a problem with my one controller. When i send (POST) data from angular to java controller using ( jason content type ) i see error which is visible in the topic. So I can't catch response from server. Controller catch this request good so in this case book is added to database correctly. Firebug show 404 error but when I checked in Postman I saw 415 Unsupported Media Type. Why there is such an exception if the controller is working properly.
This is example JSON:
{"title":"fgthbfgd","authors":[{"author_id":24,"author":"danielle steel"}],"genres":[{"genre_id":1,"genre":"Dramat"}],"description":"rthg","path_image":"19296.png"}
and this is controller:
#SuppressWarnings("finally")
#RequestMapping(value = "/rest/book", consumes = "application/json", produces = "application/json", method = RequestMethod.POST)
public MessageDTO addNewBook(#RequestBody BookDTO newBook) {
MessageDTO message = new MessageDTO();
try {
bookService.addNewBook(newBook);
message.setCheck(true);
} catch (BookTitleException e) {
message.setCheck(false);
message.setDescription("Ksiązka o tym tytule juz istnieje.");
e.printStackTrace();
} finally {
return message;
}
}
This is BookDTO
public class BookDTO implements Serializable{
private static final long serialVersionUID = -5057364006691079475L;
private Integer id;
private AuthorEntity [] authors;
private String description;
private GenreEntity [] genres;
private String title;
private String path_image;
private double rate;
private Integer progressBar;
private boolean flagRate;
private double userRate;
/* geters and seters */
}
This is Angular code:
var bookResource = $resource( $rootScope.restUrl + 'book');
var book = {
title : $scope.title,
authors : $scope.author,
genres : $scope.genre,
description : $scope.description,
path_image: null
}
service.addNewBook = function(book){
var deferred = $q.defer();
bookResource.save(book)
.$promise.then( function(data){
deferred.resolve( data );
}, function(){
deferred.reject("Error during adding new book.");
});
return deferred.promise;
}
This is only a problem in my aplication. In another case all working correctly.
You have a 415 error because you send your request without the right content-type header
var bookResource = $resource( $rootScope.restUrl + 'book',{}, {
save:{
method:"POST",
headers:{'Content-Type':'application/json; charset=UTF-8'}
}
});
I hope it will solve your problem

Categories

Resources