Java List of CompletableFutures to CompletableFuture List with allOf() - java

I have a piece of asynchronous code which contains more methods and I need to make it return CompletableFuture<List> in the end.
I need to use 2 methods:
the first method getConfigsByType() returns a Flux of type Config
the second one, which needs to be applied to every individual Config object, returns CompletableFuture of type Config.
I want to use allOf() in order to get the expected result, but I have an error and I do not know why: "no instance(s) of type variable(s) U exist so that Boolean conforms to CompletionStage". The error is at this line: .thenCompose(segmentedConfig -> finalEvents.add(segmentedConfig));
private CompletableFuture<List<Config>> getConfigs(User user) {
Queue<Config> finalEvents = new ConcurrentLinkedQueue<>();
List<CompletableFuture<Config>> completableFutureList = admin.getConfigsByType(configurationProperties.getEvents()) // returns Flux<Config>
.map(config -> {
return segmentConfig(config, user) // returns CompletableFuture<Config>
.thenCompose(segmentedConfig -> finalEvents.add(segmentedConfig));
})
.collect(Collectors.toList());
return allOf(completableFutureList)
.thenApply(list -> finalEvents);
private CompletableFuture<Void> allOf(List<CompletableFuture<Config>> futuresList) {
return CompletableFuture.allOf(futuresList.toArray(new CompletableFuture[0]));
}
private CompletableFuture<Config> segmentConfig(Config config, User user) {
return configurationApi.getSegmentedConfig(new DefaultCombinedConfigProvider<>(config), user);
}
What am I doing wrong?

You can not produce the list of results before the future created by allOf(completableFutureList) has been completed. Further, a Queue<Config> won’t become a List<Config>.
So, remove your attempt to produce the result list from the stream operation that produces the List<CompletableFuture<Config>>. Then, add an actual operation producing the result list to allOf(completableFutureList).
private CompletableFuture<List<Config>> getConfigs(User user) {
List<CompletableFuture<Config>> completableFutureList
= admin.getConfigsByType(configurationProperties.getEvents())
.map(config -> segmentConfig(config, user))
.collect(Collectors.toList());
return CompletableFuture.allOf(completableFutureList.toArray(new CompletableFuture[0]))
.thenApply(voidArg -> completableFutureList.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList()));
}

Related

How to execute CompletableFuture function and get the result either which of them is done first?

i already have create 3 function that execute query to database, then i want to get each result and add into one List of object but the result is always empty, how can i do this properly? here is what i do :
i create 3 CompletableFuture function :
private CompletableFuture<List<TransSalesOrderOnlyResponseDto>> findPureUnAssignedSO() {
return CompletableFuture.supplyAsync(() -> iSalesOrderMapper.entityToSOOnlyDto(iTransSalesOrderQdslRepository.findUnAssignedSalesOrder()));
}
private CompletableFuture<List<TransSalesOrderOnlyResponseDto>> findSOHaveItemLeftOverOnly() {
return CompletableFuture.supplyAsync(() -> {
List<TransSalesOrder> transSalesOrders = iTransSalesOrderQdslRepository.findSOHaveLeftOverButDone();
return buildTransSalesOrdersResponseNew(transSalesOrders);
});
}
private CompletableFuture<List<TransSalesOrderOnlyResponseDto>> findSalesOrderWithBpsjInDeliveryOrder() {
return CompletableFuture.supplyAsync(() -> {
List<TransSalesOrder> transSalesOrders = iTransSalesOrderQdslRepository.findSalesOrderWithBpsjInDeliveryOrder();
return buildTransSalesOrdersBpsjOnlyResponseNew(transSalesOrders);
});
}
and then here is how i try to execute that 3 function :
ATTEMPT 1, using get() :
public List<TransSalesOrderOnlyResponseDto> findUnAssignedSO() {
CompletableFuture<List<TransSalesOrderOnlyResponseDto>> future = new CompletableFuture<>();
List<TransSalesOrderOnlyResponseDto> transSalesOrdersResponseNew = new ArrayList<>();
try {
transSalesOrdersResponseNew = findSOHaveItemLeftOverOnly().get();
transSalesOrdersResponseNew.addAll(findPureUnAssignedSO().get());
transSalesOrdersResponseNew.addAll(findSalesOrderWithBpsjInDeliveryOrder().get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
return transSalesOrdersResponseNew;
}
the result still empty
ATTEMPT 2 :
public List<TransSalesOrderOnlyResponseDto> findUnAssignedSO() {
List<TransSalesOrderOnlyResponseDto> transSalesOrdersResponseNew = new ArrayList<>();
CompletableFuture<List<TransSalesOrderOnlyResponseDto>> soHaveItemLeftOverOnly = findSOHaveItemLeftOverOnly();
CompletableFuture<List<TransSalesOrderOnlyResponseDto>> pureUnAssignedSO = findPureUnAssignedSO();
CompletableFuture<List<TransSalesOrderOnlyResponseDto>> salesOrderWithBpsjInDeliveryOrder = findSalesOrderWithBpsjInDeliveryOrder();
CompletableFuture.allOf(soHaveItemLeftOverOnly, pureUnAssignedSO, salesOrderWithBpsjInDeliveryOrder)
.thenRun(() -> {
transSalesOrdersResponseNew.addAll(soHaveItemLeftOverOnly.join());
transSalesOrdersResponseNew.addAll(pureUnAssignedSO.join());
transSalesOrdersResponseNew.addAll(salesOrderWithBpsjInDeliveryOrder.join());
});
}
return transSalesOrdersResponseNew;
}
the result is always empty if i do this, even i use .get() to block the result, how do i do completablefuture properly?
Both your attempts aren't working because you call an a completion stage without waiting for the result (I'm not sure about attempt number 1 though).
I don't know the signature of all the methods, but if somewhere you are returning a CompletionStage in the supplyAsync and you don't use thenCompose instead, the function will return ignoring the result of the CompletionStage
In Attempt number 2, it's easier to see where it's wrong. It's this part:
CompletableFuture.allOf(...).thenRun(() -> ...);
It doesn't matter what you do in the thenRun part. You don't wait anywhere for the result, so it will get to the return transSalesOrdersResponseNew; immediately, even if the function you have defined in the thenRun part hasn't finished yet.
Assuming that the methods findSOHaveItemLeftOverOnly, findPureUnAssignedSO and findSalesOrderWithBpsjInDeliveryOrder are correct (we cannot know that from the details you gave use), you could rewrite the code this way:
final List<TransSalesOrderOnlyResponseDto> transSalesOrdersResponseNew = ... ;
findSOHaveItemLeftOverOnly()
.thenAccept( transSalesOrdersResponseNew::addAll )
.thenCompose( v -> findPureUnAssignedSO() )
.thenAccept( transSalesOrdersResponseNew::addAll )
.thenCompose( v -> findSalesOrderWithBpsjInDeliveryOrder() )
.thenAccept( transSalesOrdersResponseNew::addAll )
.join();
return transSalesOrdersResponseNew;
Note that I'm using .thenCompose, this will use the result of the function as the next CompletionStage in the sequence. This way you won't lose result in the process.
You could also run the find methods in parallel (if the order doesn't matter) with CompletableFuture.allOf but in that case you need to make sure to use an implementation of List that's thread safe.
It would look like this:
final List<TransSalesOrderOnlyResponseDto> transSalesOrdersResponseNew = ... // Thread-safe list implementation;
CompletableFuture.allOf(
findSOHaveItemLeftOverOnly().thenAccept( transSalesOrdersResponseNew::addAll ),
findPureUnAssignedSO().thenAccept( transSalesOrdersResponseNew::addAll ),
findSalesOrderWithBpsjInDeliveryOrder().thenAccept( transSalesOrdersResponseNew::addAll )
).join();
return transSalesOrdersResponseNew;

Java Stream processing with if / checking stream state

Given: list of Customers (with Supplier and Agency fields), String agency, String supplier.
Goal: check if any customer supports given agency AND given supplier.
I have a stream that needs to be filtered twice (by two values).
If stream is empty after first filtering, I need to check it and throw exception. If it's not empty, I need to process it through second filter (and then check again if it's not empty).
I want to avoid collecting stream to lists if it's possible (and I can't use anyMatch or count methods because they are terminal)
Currently my code look's like:
void checkAgencySupplierMapping(String agency, String supplier) {
List<Customers> customersFilteredByAgency = allCustomers.stream()
.filter(customer -> customer.getAgency().equals(agency))
.collect(toList());
if (customersFilteredByAgency.isEmpty()) throw new AgencyNotSupportedException(agency);
customersFilteredByAgency.stream()
.filter(customer -> customer.getSupplier().equals(supplier))
.findFirst().orElseThrow(() -> throw new SupplierNotSupportedException(supplier);
}
In this example I skipped some technical details about filtering (eg. parsing Supplier to String).
And I want to achieve something like this:
void checkAgencySupplierMapping(String agency, String supplier) {
allCustomers.stream()
.filter(customer -> customer.getAgency().equals(agency))
.ifEmpty( () -> throw new AgencyNotSupportedException(agency) )
.filter( customer -> customer.getSupplier().equals(supplier)
.ifEmpty( () -> throw new SupplierNotSupportedException(supplier); // or findFirst().orElseThrow...
}
Is there any Java 8 feature that will let me checking my Stream status without terminating it?
The code below is a bit ugly but work like you wish.
First we need to count how many agency of customers match with and then try found the first one supplier match. If there are no matches throw an exception, but here you will check if the cause is that no agency clients were found in order to throw the correct excaption.
AtomicInteger countAgencyMatches = new AtomicInteger(0);
allCustomers.stream()
.filter(customer -> {
if (customer.getAgency().equals(agency)) {
countAgencyMatches.incrementAndGet();
return true;
}
return false;
})
.filter(customer -> customer.getSupplier().equals(supplier))
.findFirst()
.orElseThrow(() -> {
if (countAgencyMatches.get() == 0) {
return new AgencyNotSupportedException(agency);
}
return new SupplierNotSupportedException(supplier);
});

How to flatten a stream of Single<R>

I have this code
public Stream<PaymentEntity> getPayments(List<String> paymentIds) {
return paymentIds.stream()
.flatMap(id -> paymentsRepository.getById(id))
}
paymentsRepository.getById(id) is returning Single<PaymentEntity>
But i got compile time error
no instance(s) of type variable(s) R exist so that
Single conforms to Stream
Edit, to anyone asking about Single http://reactivex.io/documentation/single.html
You can use
return paymentIds.stream()
.map(id -> paymentsRepository.getById(id).toBlocking().value());
Or in rxjava 2 you can use :
return paymentIds.stream()
.map(id -> paymentsRepository.getById(id).blockingGet());
After comment I think you can go with this solution :
List<PaymentEntity> result = new ArrayList<>();
paymentIds.forEach(id -> paymentsRepository.getById(id).toObservable().subscribe(result::add));
return result.stream();
Alternate solution:
public Stream<PaymentEntity> getPayments(List<String> paymentIds) {
return Observable.fromIterable(paymentIds)
.flatMapSingle(id -> paymentsRepository.getById(id))
.toList()
.blockingGet()
.stream();
}
You can wait until the current Single in the lambda signals a success value using .blockingGet():
return paymentIds.stream()
.map(id -> paymentsRepository.getById(id).blockingGet());
Or you can wait until all the reactive chain signals a success value in this way:
return Observable.fromIterable(paymentIds)
.flatMapSingle(paymentsRepository::getById)
.toList()
.blockingGet()
.stream();
Consider also to use Flowable: unlike Observable, it supports backpressure strategy.

RxJava2 combine multiple observables to make them return single result

How to combine multiple results emmited by observables into one result and emit it once?
I have a Retrofit service:
public interface MyService {
#GET("url")
Observable<UserPostsResult> getUserPosts(#Query("userId") int id);
#GET("url")
Observable<UserPostsResult> getUserPosts(#Query("userId") int id, #Query("page") int pageId);
}
And I have a model:
public class UserPostsResult {
#SerializedName("posts")
List<UserPost> mPosts;
#SerializedName("nextPage")
int mPageId;
}
Also I have ids List<Integer> friendsIds;
My goal is to have a method like this one:
public Observable<Feed> /*or Single<Feed>*/ getFeed(List<Integer> ids) {
...
}
It returns one Observable or Single that does the following:
Combines all getUserPosts(idFromList) to one observable
For each UserPostsResult must do:
if (userPostResult.mPageId > -1)
getUserPosts(currentUserId, userPostResult.mPageId);
And merge this result to the previous userPostResult
Return one single model as result of all operations.
Result class:
public class Feed {
List<UserPost> mAllPostsForEachUser;
}
EDIT (More details):
My client specifications was that I must take from social network user posts with no logging in, no token requesting. So I must parse HTML pages. That's why I have this complex structure.
EDIT (Partial solution)
public Single<List<Post>> getFeed(List<User> users) {
return Observable.fromIterable(users)
.flatMap(user-> mService.getUserPosts(user.getId())
.flatMap(Observable::fromIterable))
.toList()
.doOnSuccess(list -> Collections.sort(list, (o1, o2) ->
Long.compare(o1.getTimestamp(), o2.getTimestamp())
));
}
This solution doesn't include pages problem. Thats why it is only partial solution
There are a number of operators which transform things into other things. fromIterable() will emit each item in the iterable, and flatMap() will convert one type of observable into another type of observable and emit those results.
Observable.fromIterable( friendsIds )
.flatMap( id -> getUserPosts( id ) )
.flatMap( userPostResult -> userPostResult.mPageId
? getUserPosts(currentUserId, userPostResult.mPageId)
: Observable.empty() )
.toList()
.subscribe( posts -> mAllPostsForEachUser = posts);
If you need join two response in one you should use Single.zip
Single.zip(firsSingle.execute(inputParams), secondSingle.execute(inputPrams),
BiFunction<FirstResponse, SecondResponse, ResponseEmitted> { firstResponse, secondResponse ->
//here you put your code
return responseEmmitted
}
}).subscribe({ response -> },{ })

rxjava: emitting one result of all observables instead of multiple

The situation is that my getData() method is called with a parameter of list of Strings. For each of the strings I need to perform some action in getDataPerItem(). At the end the results of each calls to getDataPerItem() should be merged into one big List (that keeps the order of results) and getData() should emit all result in a single list. With the current approach it will emit one List for each identifier separately. How to merge all the results into one?
public Observable<List<Model>> getData(List<String> identifiers) {
return Observable.from(identifiers).flatMap { identifier -> getDataPerItem(identifier) };
}
public Observable<List<Model>> getDataPerItem(String identifier) {
return xxx;
}
There are several ways to accomplish this:
One solution is to use concatMapIterable and then collect everything in a list:
public Single<List<Model>> getData(List<String> identifiers) {
return Observable
.from(identifiers)
.concatMap(identifier -> getDataPerItem(identifier))
.concatMapIterable(it -> it)
.toList();
}
Another solution is to reduce into an ArrayList:
public Single<List<Model>> getData(List<String> identifiers) {
return Observable
.from(identifiers)
.concatMap(identifier -> getDataPerItem(identifier))
.reduce(new ArrayList<Model>(), (list, next) -> list.addAll(next));
}
Note that the return type is Single because it only emits one item: the list containing everything.

Categories