compile error while using java 8 api to stream on CompletableFuture - java-8

This works:
public Long getMaxSalary(List<CompletableFuture<EmployeeData>> futures) {
CompletableFuture<Void> allDoneFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
CompletableFuture<List<EmployeeData>> employeeDataList = allDoneFuture.thenApply(v ->
futures.stream()
.map(f -> f.join())
.collect(Collectors.toList()));
List<EmployeeData> rc = employeeDataList.get();
OptionalLong op = rc.stream().mapToLong(r -> r.salary()).max();
return op.getAsLong();
}
trying to make this concise throwing compiler errors in IDE. I cannot figure out what the error is. I am trying to combine it in one stream.
public Long getMaxSalary(List<CompletableFuture<EmployeeData>> futures) {
CompletableFuture<Void> allDoneFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
return allDoneFuture.thenApply(v ->
futures.stream()
.map(f -> f.join())
.mapToLong(r -> r.salary())
.max()).getAsLong();
}

There is no point in using allOf and thenApply if you are going to block the current thread anyway with an immediate .get()
return futures.stream()
.map(CompletableFuture::join)
.mapToLong(EmployeeData::salary)
.max()
.getAsLong(); // or throw if futures is empty
allOf approach would be useful if you wanted to return CompletableFuture<Long> and let the clients of your method decide when and in what thread to await completion.

Try this out,
return allDoneFuture.thenApply(v -> futures.stream().map(f -> f.join())).get()
.mapToLong(empData -> empData.salary()).max().getAsLong();

Related

Perform two queries at the same time (reactive)

I know it is asynchronous and therefore the values I want to save will be null (I checked). How can I fix this so that everything is saved correctly?
I want to save a Document and at the same time get that ID generated by MongoDB to save to another API.
public Mono<BankAccountDTO> save(BankAccountDTO bankAccount) {
return mongoRepository.save(AppUtils.dtoToEntity(bankAccount))
.doOnNext(d -> {
CustomerRoleDTO cDto = new CustomerRoleDTO();
cDto.setBankAccountid(d.getId());
cDto.setClientId(bankAccount.getCustomerId());
webClient.post()
.uri("http://localhost:9292/api/v1/example")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.body(Mono.just(cDto), CustomerRoleDTO.class)
.retrieve()
.bodyToMono(CustomerRoleDTO.class);
}).map(AppUtils::entityToDTO);
}
you are breaking the chain by not handling the return from your wbClient call, so that reactor can't fulfill assembly time.
(i have not checked against a compiler, but something like this)
public Mono<BankAccountDTO> save(BankAccountDTO bankAccount) {
return mongoRepository.save(AppUtils.dtoToEntity(bankAccount))
.flatMap(d -> {
CustomerRoleDTO cDto = new CustomerRoleDTO();
cDto.setBankAccountid(d.getId());
cDto.setClientId(bankAccount.getCustomerId());
// you cant ignore the return
return webClient.post()
.uri("http://localhost:9292/api/v1/example")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.body(Mono.just(cDto), CustomerRoleDTO.class)
.retrieve()
.bodyToMono(CustomerRoleDTO.class)
.thenReturn(d);
});
}
here you can read more about assembly time.
Assembly vs Subscription
Use flatmap instead of doOnNext

Stored Procedure call with CompletableFuture

I have a Springboot API that makes a maximum of 6 Stored Procedure calls using the callable statement. I want to make this call asynchronous. Is it possible to achieve this using CompleteableFuture(java8)???
Database connections are typically not thread-safe. Are you planning to use one connection per call?
If yes, following code will execute the callable statements in parallel. Please note I have used vavr library to simplify the exception handling.
public List<Boolean> concurrentCalls(Supplier<Connection> connectionSupplier, List<String> statements) {
Function<String, Either<Throwable, Boolean>> executeStatement = statement ->
Try.of(() -> connectionSupplier.get()
.prepareCall(statement)
.execute())
.toEither();
List<CompletableFuture<Boolean>> completableFutures = statements.stream()
.map(statement ->
CompletableFuture.supplyAsync(() -> executeStatement.apply(statement))
.thenApply( Either::get) // Handle exceptions as required
)
.collect(Collectors.toList());
return CompletableFuture.allOf( completableFutures.toArray( new CompletableFuture[0]))
.thenApply( any ->
completableFutures
.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList())
)
.join();
}

Filtering and default value in Reactor

I'm presently experiencing a really strange and frustrating issue at the moment.
I have some code that is being tested that runs through a reactive call chain containing a series of filtering operations.
As the test runs through the code and a 'false' value is returned, the code still passes through to the next call in the chain instead of just returning.
Since I'm still a 'reactive newbie' I'm figuring I'm probably not doing something incorrectly here in the reactive code chain.
Here is the code:
private Mono<GetCardNumberServiceResponseData> updateCardNumberIfLastFourValidAndShaIsNull(Card card, GetCardNumberServiceResponseData responseData) {
return Mono.just(responseData)
.filter(response -> isValidLastFour(card, response))
.defaultIfEmpty(responseData)
.filter(response -> shaIsNull(card))
.defaultIfEmpty(responseData)
.flatMap(response -> updateCardNumber(card, response));
}
This is the portion that's not evaluating correctly:
.filter(response -> isValidLastFour(card, response))
This is what 'isValidLastFour' currently looks like:
private boolean isValidLastFour(Card card, GetCardNumberServiceResponseData responseData) {
// String cardNumberFromResponse = responseData.getCardNumber();
// String lastFourFromResponse =
// cardNumberFromResponse.substring(cardNumberFromResponse.length() - 4);
// return card.getLastFour().equals(lastFourFromResponse);
return false;
}
So presently I just have it hard-coded to return 'false', but as I step through the test with the debugger, the execution just passes right through as if 'true' is being returned, so I'm really just at a loss at what might be causing this behavior.
As always, any and all help is always greatly appreciated!
If you want responseData to be the default value, in case there is an empty Mono, you have to put defaultIfEmpty at the end of the chain:
return Mono.just(responseData)
.filter(response -> isValidLastFour(card, response))
.filter(response -> shaIsNull(card))
.flatMap(response -> updateCardNumber(card, response))
.defaultIfEmpty(responseData);
Even better, you can merge those filters:
return Mono.just(responseData)
.filter(response -> isValidLastFour(card, response) && shaIsNull(card))
.flatMap(response -> updateCardNumber(card, response))
.defaultIfEmpty(responseData);

Issue with Flux

Cannot convert from Mono to Flux
I'm getting this error while trying below code
public Flux<PortCall> updateByFindById(String gsisKey, PortCall portCall) {
return portCallRepository.findAllByVesselCodeOrderBySequenceNo(portCall.getVesselCode())
.switchIfEmpty(
Mono.error(new DataNotFoundException(HttpStatus.NO_CONTENT, PortCallConstants.ROUTE_NOT_FOUND)))
.collectList().flatMap(list -> {
if (list.size() > 0)
return Mono.just(list)
.switchIfEmpty(Mono.empty());
});
}
Please suggest a workaround
Using collectList() and check for an empty list is redundant, because if your repository method doesn't emit any elements, it automatically calls switchIfEmptyoperator.
Just leave it like that:
return portCallRepository.findAllByVesselCodeOrderBySequenceNo(portCall.getVesselCode())
.switchIfEmpty(
Mono.error(new DataNotFoundException(HttpStatus.NO_CONTENT, PortCallConstants.ROUTE_NOT_FOUND)));

Get Reactor Subscriber Context in doOnSubscribe, doOnSuccess and doOnError

I'm trying to implement the Reactor Subscriber Context (http://projectreactor.io/docs/core/release/reference/#context) so I can pass values from my SLF4J MDC into a Flux where I then can use the values for logging.
I use the subscriberContext() method to set the value like:
someFlux().subscriberContext(Context.of(MDC_ATTRIBUTE, MDC.get(MDC_ATTRIBUTE)));
I also can access the context in the chain. For example with a flatMap:
.flatMap(r -> Mono.subscriberContext().map(ctx -> {
String name = ctx.getOrDefault(MDC_ATTRIBUTE_NAME, "NO CTX");
return r;
}))
Also doOnEach() works:
.doOnEach(signal -> {
Context ctx = signal.getContext();
if (signal.isOnNext()) {
try (MDC.MDCCloseable closeable = MDC.putCloseable(MDC_ATTRIBUTE_NAME, ctx.getOrDefault(MDC_ATTRIBUTE_NAME, "MAAAAN"))) {
log.debug("FINISHED: {}", requestName);
}
}
})
There is just one problem with it. I want to log something in doOnSubscribe, doOnError and in doOnSuccess. While I could use doOnEach to check for signal.isOnNext() or signal.isOnComplete(), I found out that signal.isOnSubscribe() is never called.
So the question is: How can I get the context in doOnSubscribe() or is this simply not possible?
It is possible, not in 100% use cases and with a bit of a trick:
Flux.just("foo")
.doOnSubscribe(sub -> {
Scannable actual = Scannable.from(sub).scan(Scannable.Attr.ACTUAL);
if (actual instanceof CoreSubscriber) {
Context context = ((CoreSubscriber) actual).currentContext();
System.out.println(context);
}
})
.map(v -> "value: " + v) //below or above doOnSubscribe is fine
.subscriberContext(Context.of("foo", "bar")) //MUST be below doOnSubscribe
.blockLast();

Resources