How to throw an exception properly when do Flux processing? - java

Existing code that I have:
private Flux<Integer> testGetFluxTestData() {
return Flux.just(new TestData(1), new TestData(2))
.collectList()
.map(list -> list.stream()
.map(TestData::getId)
.collect(Collectors.toList()))
.flatMapMany(Flux::fromIterable);
}
I want to enrich existing code and throw an exception when some not allowed data received, I made the following changes:
private Flux<Integer> testGetFluxTestData2() {
return Flux.just(new TestData(1), new TestData(2))
.collectList()
.map(list -> {
return !list.contains(new TestData(1)) ?
list.stream()
.map(TestData::getId)
.collect(Collectors.toList()) :
Flux.error(new IllegalTestDataException("illegal test data 1"));
})
.flatMapMany(Flux::fromIterable);
}
but my implementation even noncompilable due to the following line:
Flux.error(new IllegalTestDataException("illegal test data 1"));
Could you please suggest, how to handle exception throwing for my particular scenario?

You are attempting to map from a List<TestData> to either a List<Integer> or a Flux<?> (error), which makes the desired result type ambiguous. Returning a reactive type in a mapping function is generally not desired (you'd want to do that in a flatmapping function).
(side note: even if you were in a flatMap, it wouldn't work either because at that point you're in Mono API due to collectList, so Mono.flatMap expects a Mono result to the Function).
Note that the map operator catches exceptions from the lambda and turn them into an onError signal, so technically you could replace the Flux.error with a throw.
Otherwise, you'd need to turn the map into a flatMap and the Flux.error into a Mono.error, for the reasons stated above.

Related

How to save method returned mono in Reactor's Context to use it the next time the method is called in this reactor stream?

I have a method that returns data from some repo. Of course - these data can be changed at any given moment. I want to ensure that the data returned from the method remains consistent during a chain of operations (the method can also be called from another component during the chain). for example:
fetchDataThanCanBeChanged()
.flatMap(...)
.doOnNext(...)
.doOnSuccess(...)
.map(...)
.zipWith(...)
.flatMap(...)
.then(fetchDataThanCanBeChanged())......
I want to be sure that the second call to fetchDataThanCanBeChanged() returns the same data as the first.
I tried to wrap fetchDataThatCanBeChanged with method with Mono.deferContextual:
private Mono<Object> fetchDataUsingCache() {
return fetchDataThatCanBeChanged()
.flatMap(s ->
Mono.deferContextual(ctx -> {
System.out.println("cached snapshot=" + ctx.getOrEmpty("KEY"));
return Mono.just(s);
}).contextWrite(ctx ->
ctx.put("KEY", ctx.getOrDefault("KEY", s))
));
}
and call it twice:
fetchDataUsingCache()
.flatMap(d -> change(d))
.doOnNext(p -> saveChangedDataInRepo(p))
.delayElement(Duration.ofMillis(2000))
.fetchDataUsingCache()
.block();
but the content of fetchDataThatCanBeChanged() is only executed once and the returned data is the updated.
any idea for a solution???
Many thanks in advance!
finally I wrapped my code where I need the old fetched data with Mono.deferContextual and wrote the data to context like that:
fetchDataThanCanBeChanged()
.flatMap(s -> Mono.deferContextual(ctx ->
changeDataInRepo()
.doOnSuccess(b -> fetchDataThanCanBeChanged())
.thenReturn(doWhateverNeedWithOldData(ctx.getOrEmpty("KEY"))))
.contextWrite(ctx -> ctx.put("KEY", s))))
.block();

Perform a filter within a function call

Is there a way I could perform a filter within the steps of the following function call?
The following works fine but I wish to add a null/empty check on getResults which returns a list of Results object.
I could try to .stream() on it like getResults().stream().filter();
But kind of pointless since getResults().stream() could potentially throw a null pointer already.
Also this returns a stream of Result. But I want to do a check on the List of Result itself, like:
.filter(CollectionUtils::isNotEmpty)
Is there a way to do this?
public MyFunc(Helper helper) {
Function<String, HttpEntity<Request>> createRequestFunction = helper::createRequest;
this.fetch = createRequestFunction
.andThen(helper::getResponse)
.andThen(Response::getQuery)
.andThen(QueryResult::getResults)
// I want to make a filter here .filter(CollectionUtils::isNotEmpty) to ensure
// getResults (It is a List<Result> type) is not null or empty
.andThen(results -> results.stream()
.map(Result::getKey)
.filter(StringUtils::isNotBlank)
.map(s -> s.split("\\|"))
.filter(data -> data.length == 2)
.map(data -> data[1])
.collect(Collectors.toList()));
}
createRequestFunction has type of Function<String, HttpEntity<Request>>
Function itself doesn't have a filter operation. It is possible to either append additional function via andThen or prepend via compose.
What is expected behaviour if QueryResult::getResults is null? What is the value of this.fetch should be?
One possible option is to wrap result of QueryResult::getResults into an Optional. Something similar to this:
this.fetch = createRequestFunction
.andThen(helper::getResponse)
.andThen(Response::getQuery)
.andThen(QueryResult::getResults)
.andThen(Optinal::ofNullable);
so the result of this.fetch is Optinal<List<Result>> and it is possible to execut different logic based on the fact if optional is empty or not.
e.g
return
this.fetch // Optinal<List<Result>>
.getOrElse(Collections.emptyList()) // value of optional if it is not empty, or empty list otherwise
.stream()
.map(Result::getKey)
.filter(StringUtils::isNotBlank)
.map(s -> s.split("\\|"))
.filter(data -> data.length == 2)
.map(data -> data[1])
.collect(Collectors.toList())

Elegant way to throw exception on empty stream

I am fetching list of clients. Then I map enitity to dto and return result. I would like to throw exception on empty client list, but also I want to avoid if statement in code.
I can wrap list in optional but I believe there is more elegant solution.
Set<ClientDto> clients = Optional.of(repo.findByNumber(number))
.filter(CollectionUtils::isNotEmpty)
.orElseThrow(() -> new NotFoundException())
.stream()
.map(client -> new ClientDto(client.getName()))
.collect(Collectors.toSet());
Is there any cleaner solution? Because there is few usless chains in my code. I am even now starting to think that pure if would be more readable.
I don't know how you can do it with pure streams but you could define a method which returns a stream of the list and throw an exception if its empty.
private <T> Stream<T> throwIfEmpty(List<T> list) {
if(list.isEmpty()) {
throw new IllegalArgumentException("List must not be empty");
}
return list.stream();
}
then you could use it the following way:
List<Client> clients = throwIfEmpty(repo.findByNumber(number))
.map(client -> new ClientDto(client.getName()))
.collect(Collectors.toSet());
maybe this solution makes you happy :)

RxJava2: Emit error only when all observables emitted error

I have multiple observables doing network calls and I need to emit error from combiner observable only when all observables emitted error. If at least one observable completes then the result should be passed.
My current stream functions like this:
Observable.fromIterable(list)
.flatMap{networkObservable}
.reduce{combine result of all network observables to same list}
I could just do:
Observable.fromIterable(list)
.flatMap{networkObservable.onErrorReturnItem(errorItem)}
.reduce{check if all items are error items and return error item otherwise combine result of
all network observables to same list}
But I wanted to know if there's a better way to handle this case.
If I understand correctly, if there is at least one item from any of the networkObservables, errors should be ignored, right? In this case, you could detect the onNexts emitted, then in the error handler, suppress the error:
Observable.using(
() -> new AtomicBoolean(),
hasItem -> Observable.fromIterable(list)
.flatMap(v -> networkObservable, true) // delay errors
.doOnNext(v -> {
if (!hasItem.get()) {
hasItem.set(true);
}
})
.onErrorResumeNext(error -> {
if (hasItem.get()) {
return Observable.empty();
}
return Observable.error(error);
}),
() -> { } // no need to clean up the AtomicBoolean
)

Getting Incompatible types error while trying to map a list

I have a FeeAccount list that I would like to fill. I want to use .stream.map() to get it done. What I've managed to do is to make a method that would map my list and return it. I've written this code using some other examples I have found online. My problem is that somehow it returns a list that is incompatible with List.
I am getting an error: Incompatible types. Required List but 'map' was inferred to Stream: no instance(s) of type variable(s) R exist so that Stream conforms to List
As I understand the problem is with the part where I use collect(Collectors.toList()). But I am not sure. I don't even clearly understand what the error message means.
Maybe someone can explain what am I doing wrong? Is it with the .stream.map()? Because I never used it before. Or maybe the problem is somewhere else.
Method(List<contract> contractList){
List<FeeAccount> feeAccounts = new ArrayList<>();
feeAccounts = contractList
.stream()
.map(contract -> {
List<Fee> monthlyFees=...;
return monthlyFees.stream()
.map(monthlyFee -> {
FeeAccount account = new FeeAccount();
account.setFeeCode(monthlyFee.getFeeCode());
account.setDebtorAccount(contract.getDebtorAccount());
return account;
}).collect(Collectors.toList());
});}
You have two nested map operations. The outer transforms a contract to a List<FeeAccount>, and the inner transforms a Fee to a FeeAccount.
Hence, your pipeline results in a Stream<List<FeeAccount>> without a terminal operation.
If you add a .collect(Collectors.toList()) in the end, you'll get a List<List<FeeAccount>>.
If you want to merge all those inner lists into a single output list, you should use flatMap.
To obtain a flat List:
List<FeeAccount> feeAccounts =
contractList.stream()
.flatMap(contract -> {
List<Fee> monthlyFees=...;
return monthlyFees.stream()
.map(monthlyFee -> {
FeeAccount account = new FeeAccount();
account.setFeeCode(monthlyFee.getFeeCode());
account.setDebtorAccount(contract.getDebtorAccount());
return account;
});
})
.collect(Collectors.toList();
map() is an intermediate operation in a stream pipeline (please look at Stream operations and pipelines), which means that it returns a stream.
feeAccounts = contractList
.stream()
.map(...) // result of this operation is Stream<<List<FeeAccount>>
and not a List<FeeAccount>
You are missing a terminal operation like .collect(Collectors.toList() :
List<FeeAccount> feeAccounts = contractList
.stream()
.flatMap(monthlyFees -> monthlyFees.stream()
.map(monthlyFee -> {
FeeAccount account = new FeeAccount();
account.setFeeCode(monthlyFee.getFeeCode());
account.setDebtorAccount(contract.getDebtorAccount());
return account;
})
.collect(Collectors.toList());
flatMap transforms Stream<Stream<FeeAccount>> into just Stream<FeeAccount>

Categories

Resources