How can I flatMapMany with multiple web calls and conditionally signal complete? - spring

I need to write a method which does
Gets a Location header form an endpoint
Generates a series of Some which each should retrieved from the Location fetched from the first endpoint.
Need to return a Flux<Some>
Which looks like this.
private WebClient client;
Flux<Some> getSome() {
// Get the Location header from an endpoint
client
.post()
.exchange()
.map(r -> r.headers().header("Location"))
// Now I need to keep invoking the Location endpoint for Some.class
// And Some.class has an attribute for pending/completion status.
// e.g.
while (true)
final Some some = client
.get()
.url(...) // the Location header value above
.retrieve()
.bodyToMono(Some.class)
.block()
;
}
}
How can I map the Mono<String> to a Flux<Some> while using the Some#status attribute for completion?
// should return Flux<Some>
client
.post()
.exchange()
.map(r -> r.headers().header("Location"))
.flatMapMany(location -> {
// invokes location while retrieved Some#status is not `completed`
// ##?
})
;

inside the flatMapMany, perform the call that retrieves the Location. apply a repeat().takeUntil(loc -> loc.complete()) to it (still inside the flatMapMany lambda)

Related

How can I use wiremock to test webclients?

What the below code does is that it gets items for specific(using client1) customer and delete them by making another endpoint, client2. The first endpoint provides pagination.
Now, I am having issue with testing it. As you can see, I am using wiremock but it does not seem working properly as the last endpoints never called. I have provided the response body for the first stub, was expecting the next endpoints to be triggered. I tried to debugging by putting break point to see if they are called, but the code never reach the section where the two endpoint are triggered. I am not sure the way I did is correct. Is this the correct approach to test using wiremock?
public void deleteItems(String id) {
client1.get()
.uri(urlBuilder -> urlBuilder
.path(CLIENT_ONE_ENDPOINT)
.queryParam("email", id)
.build())
.retrieve()
.bodyToMono(Items.class)
.expand(items -> {
while (!items.isOnLastPage()) {
int nextPage = items.getCurrentPage() + 1;
return client1.get()
.uri(builder -> builder
.path(CLIENT_ONE_ENDPOINT)
.queryParam("email", id)
.queryParam("pageNumber", nextPage)
.build())
.retrieve()
.bodyToMono(Items.class);
}
return Flux.empty();
})
.flatMapIterable(Items::getResults)
.map(or -> {
return client2.delete()
.uri(CLIENT_TWO_ENDPOINT + or.getId())
.retrieve()
.bodyToMono(ResponseEntity.class)
.subscribe();
})
.subscribe();
}
wiremock test
#Test
void itemsAreRemoved() {
String email = "example#example.com";
String itemId = "001";
WireMock.configureFor(PORT);
wireMockServer.stubFor(get(urlPathEqualTo(CLIENT_ONE_ENDPOINT))
.withQueryParam("email", equalTo(email))
.withHeader("Content-Type", equalTo("application/json"))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withBody(loadResponse(itemId))
.withStatus(200)));
wireMockServer.stubFor(get(urlPathEqualTo(CLIENT_ONE_ENDPOINT))
.withQueryParam("email", equalTo(email))
.withQueryParam("pageNumber", equalTo("1"))
.withHeader("Content-Type", equalTo("application/json"))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withStatus(200)));
wireMockServer.stubFor(delete(urlPathEqualTo(CLIENT_TWO_ENDPOINT + itemId)));
service.deleteItems(email);
verify(getRequestedFor(urlPathEqualTo(CLIENT_ONE_ENDPOINT))
.withQueryParam("email", equalTo(email)));
verify(getRequestedFor(urlPathEqualTo(CLIENT_ONE_ENDPOINT))
.withQueryParam("email", equalTo(email))
.withQueryParam("pageNumber", equalTo("1")));
verify(exactly(1), deleteRequestedFor(urlPathEqualTo(CLIENT_TWO_ENDPOINT + itemId)));
}

How to process comma separated string of ids using Mono<String>

I have a api which gives me a list of comma separated ids and I am using Spring Boot WebClient to make a call to the same and process the response.
The API response is [1,2,3,4] in plain/text (not json) and I am doing the following to process the results -
Mono<String> ids = client.get()
.uri("http://localhost:8080")
.retrieve()
.bodyToMono(String.class);
I wanted to know how can I process the items inside the ids variable from thereon.
newbie to this api.
You could convert response directly into Flux, and process items one by one
webClient.get()
.uri("http://localhost:8080")
.retrieve()
.bodyToFlux(Integer.class)
.doOnNext(id -> {
System.out.println(id);
// process id...
})
.subscribe();
Another possible option is to convert Mono<String> into Flux<Integer> using method flatMapIterable:
ids.flatMapIterable(line -> Arrays.asList(line.replace("[", "").replace ("]", "").split(",")))
.doOnNext(id -> System.out.println(id))
.subscribe();

Multiple Mono and common subscriber

I am a newbie to reactive programming in Java. I plan to use spring-webclient instead of restclient as the latter is being decommissioned. I have a situation when I make several http post requests to different endpoints and the response structure is identical. With webclient code as below,
List<Mono<CommonResponse>> monolist = new ArrayList<>();
for(String endpoint : endpoints) {
Mono<CommonResponse> mono = webClient.post()
.uri(URI.create(endPoint))
.body(Mono.just(requestData), RequestData.class)
.retrieve()
.bodyToMono(CommonResponse.class);
monolist.add(mono);
}
I get a mono per request. As the response is common, I would like each mono to be subscribed a common method, but how can I distinguish the endpoints, assuming that the response data is not helping.
Can I pass additional arguments to the method while subscribing?
You can do this in following way. If you have many monos you can treat team as flux which actually means that you have many of Mono. Then you can subscribe all of them with single method. To pass to subscribing method some extra arguments like information about endpoint, you can create extra object with additional information.
Flux<ResponseWithEndpoint> commonResponseFlux = Flux.fromIterable(endpoints)
.flatMap(endpoint -> webClient.post()
.uri(URI.create(endpoint))
.body(Mono.just(requestData), RequestData.class)
.retrieve()
.bodyToMono(CommonResponse.class)
.map(response -> new ResponseWithEndpoint(response, endpoint)));
...
class ResponseWithEndpoint {
CommonResponse commonResponse;
String endpoint;
public ResponseWithEndpoint(CommonResponse commonResponse, String endpoint) {
this.commonResponse = commonResponse;
this.endpoint = endpoint;
}
}

Stream an object, Send request with completable future and assign result to the object

I have a list of Object Signers. For each of this signers I need to make a ReST request to get their signing URL. I am trying to do it with completable futures so all ReST requests can be send in parallel, then I need to set that URL in each of the signers, so this operation will not return new signers just update the ones that I am already iterating.
I have this code that is already working, but I think that could be improved.
List<Signer> signers=......
List<CompletableFuture> futures = signers.stream()
.map(signer -> CompletableFuture.completedFuture(signer))
.map(future -> future.thenCombine( CompletableFuture.supplyAsync(()-> signatureService.getSigningUrl(future.join().getSignerId())),
(signer, url) -> {
signer.setUrl(url);
return url;
}
)).collect(toList());
futures.stream()
.map(CompletableFuture::join)
.collect(toList());
Could I replace this
futures.stream()
.map(CompletableFuture::join)
.collect(toList());
With this?
futures.stream().forEach(CompletableFuture::join)
I don't like to return that because it was already used setting it in the signer. and don't like the second collect(toList()) because I am not trying to collect anything at that time.
What other implementation would you use?
No. futures.stream().forEach(CompletableFuture::join) returns void whereas futures.stream().map(CompletableFuture::join).collect(toList()); returns CompletableFuture<List<?>>.
They both are meant for different purposes. But both does one thing common(i.e, blocks the main thread till all the completablefutures are finished).
I would write your same code bit differently using CompletableFuture.allOf.
Stream<CompletableFuture<String>> streamFutures = signers.stream()
.map(signer -> CompletableFuture.completedFuture(signer))
.map(future -> future.thenCombine(CompletableFuture.supplyAsync(() -> signatureService.getSigningUrl(future.join().getSignerId())),
(signer, url) -> {
signer.setUrl(url);
return url;
}
));
CompletableFuture<String> [] futureArr = streamFutures.toArray(size -> new CompletableFuture[size]);
List<CompletableFuture<String>> futures = Arrays.asList(futureArr);
CompletableFuture<Void> allFuturesVoid = CompletableFuture.allOf(futureArr);
allFuturesVoid.join();
CompletableFuture<List<?>> allCompletableFuture = allFuturesVoid.thenApply(future -> futures.stream().map(completableFuture -> completableFuture.join()).collect(Collectors.toList()));
There is good tutorial here https://m-hewedy.blogspot.com/2017/02/completablefutureallof-that-doenst.html and here https://medium.com/#senanayake.kalpa/fantastic-completablefuture-allof-and-how-to-handle-errors-27e8a97144a0.

How to convert the body of a client response to a Mono of a certain type?

So, I send a request using the WebClient and after retrieving the response using exchange() I need to extract the body to a Mono of Object_1. The function needs to return Mono>. Normally you would use bodyToMono(SomeObject.class) but this doesn't work in this case.
public Mono<Object1<Object2>> getAll(String someParam) {
return WebClient.create(this.baseUrl)
.get()
.uri(uriBuilder -> uriBuilder.path("/some_path")
.queryParam("someParam", someParam)
.build())
.exchange()
.flatMap(clientResponse -> clientResponse.bodyToMono(????));
}
I am trying to figure out what to put inside the bodyToMono()
I believe you can do that with the overload of bodyToMono that takes a ParameterizedTypeReference.
ParameterizedTypeReference<Object1<Object2>> typeRef =
new ParameterizedTypeReference<Object1<Object2>>() {};
// . . .
.flatMap(clientResponse -> clientResponse.bodyToMono(typeRef));
You could write it inline, if you prefer and don't find it too hard to read:
.flatMap(clientResponse -> clientResponse.bodyToMono(
new ParameterizedTypeReference<Object1<Object2>>() {}));

Resources