Spring-WebFlux Flux fails with Context - java

I want to use Context in my Flux pipe to bypass filtering.
Here's what I have:
public Flux<Bar> realtime(Flux<OHLCIntf> ohlcIntfFlux) {
return Flux.zip(
ohlcIntfFlux,
ohlcIntfFlux.skip(1),
Mono.subscriberContext().map(c -> c.getOrDefault("isRealtime", false))
)
.filter(l ->
l.getT3() ||
(!l.getT2().getEndTimeStr().equals(l.getT1().getEndTimeStr())))
.map(Tuple2::getT1)
.log()
.map(this::
}
which is input to this this:
public void setRealtime(Flux<Bar> input) {
Flux.zip(input, Mono.subscriberContext())
.doOnComplete(() -> {
...
})
.doOnNext(t -> {
...
})
.subscribe()
}
I can tell my code in ... is not failing, I can even access the Context map, but when the first iteration completes, I get:
onContextUpdate(Context1{reactor.onNextError.localStrategy=reactor.core.publisher.OnNextFailureStrategy$ResumeStrategy#35d5ac51})
and subscriber disconnects.
So my question is whether I am using it right and what can be an issue here?
EDIT:
I have tried to repeat() the Mono.subscriberContext() when I'm using value out of it:
return Flux.zip(
ohlcIntfFlux,
ohlcIntfFlux.skip(1),
Mono.subscriberContext()
.map(c -> c.getOrDefault("isRealtime", new AtomicBoolean())).repeat()
)
.filter(l ->
l.getT3().get() ||
(!l.getT2().getEndTime().isEqual(l.getT1().getEndTime())))
.map(Tuple2::getT1)
and set the AtomicBoolean to the context on the subscriber end and just change the value inside this variable reference, when I need the signal on the upstream, but it doesn't change at all:
input
.onErrorContinue((throwable, o) -> throwable.getMessage())
.doOnComplete(() -> {
System.out.println("Number of trades for the strategy: " + tradingRecord.getTradeCount());
// Analysis
System.out.println("Total profit for the strategy: " + new TotalProfitCriterion().calculate(timeSeries, tradingRecord));
})
.doOnNext(this::defaultRealtimeEvaluator)
.subscriberContext(Context.of("isRealtime", isRealtimeAtomic))
.subscribe();
at least with repeat the Flux doesn't disconnect but the value I'm getting out of it is not being updated. No other clues I have.
Spring-webflux: 2.1.3.RELEASE

this works:
input
.onErrorContinue((throwable, o) -> throwable.getMessage())
.doOnComplete(() -> { ... }
.flatMap(bar -> Mono.subscriberContext()
.map(c -> Tuples.of(bar, c)))
.doOnNext(this::defaultRealtimeEvaluator)
.subscriberContext(Context.of("isRealtime", new AtomicBoolean()))
.subscribe();
so the point is to set AtomicBoolean in my case as the cotnext and then extract this variable out of the context if you want to change it's value. the same on the upstream flux.

Related

How to resolve method 'getT1' in 'Void'

I have the following method:
public void countLetters() {
Flux.just("alpha", "bravo", "charlie")
.map(String::toUpperCase)
.flatMap(s -> Flux.fromArray(s.split("")))
.groupBy(String::toString)
.sort(Comparator.comparing(GroupedFlux::key))
.flatMap(group -> Mono.just(group.key()).and(group.count()))
.map(keyAndCount ->
keyAndCount.getT1() + " => " + keyAndCount.getT2())
.subscribe(System.out::println);
}
It gives me these errors:
Cannot resolve method 'getT1' in 'Void'
Cannot resolve method 'getT2' in 'Void'
I'm not sure what this means. Is it because my method is void or is it some other reason?
Instead of
.flatMap(group -> Mono.just(group.key()).and(group.count()))
You need
.map(group -> new Tuple2<>(group.key(), group.count()))
The issues in your flatMap:
It doesn't need to be a flat map at all. You are mapping your response in a "1 to 1" fashion without any further reactive streams, so the Mono.just isn't needed.
Mono.and joins 2 Monos and will produce a new Mono<Void> which is a mono that only completes (when all joined monos complete), meaning it has no result (doesn't emit any values). This means that .map and .flatMap on this mono will accept a Void as param (main reason for your compile time error). Not just that, but they will never be called, as Mono<Void> don't emit any Void values, they just complete (similar to Completable from RxJava).
For example, if you have
Mono<File> downloadFile = ...;
Mono<Long> calculateNumber = ...;
and you perform
Mono<Void> test = downloadFile.and(calculateNumber);
You would create a new Mono that completes when both monos complete, but throws away both values!
So:
test.map(v -> {
// v is type of "Void", and this map will never be called!
System.out.println("this will never be printed!");
return 0;
}).doFinally((signalType) -> {
System.out.println("this will be called!");
}).subscribe(
v -> System.out.println("wont be called, nothing is emitted!"),
err -> System.out.println("this might be called, if the stream emits an error"),
() -> System.out.println("this will be called, as its a completion handler!")
);
The mono test will terminate/complete after both monos joined with and complete.
If you really want a flatMap with Mono.just-ing the values of the tuple, you could use Mono.zip.
Tuple2 Cannot be used directly.
So,
Instead of
.flatMap(group -> Mono.just(group.key()).and(group.count()))
You need
.flatMap(group -> Mono.just(group.key()).zipWith(group.count()))
zipWith function will combine the result from this mono and another into a Tuple2.

Java Lambda for break inside two for loops

I am trying to convert an iterative block of code in Java 8 to functional. The functional approach is unable to find the matching message in the set shared.
List<Optional<Message>> allMessages = new ArrayList<>();
Set<Status> allStatuses = getAllStatuses();
//Iterative : Working
Set<StatusMessage> set = new HashSet<>(STATUS_MESSAGE.values());
for (StatusMessage statusMessage : set) {
for (Status status : statusMessage.getStatusAndInfo().keySet()) {
Optional<Message> message = MessageBuilder.createMessage(allStatuses, status, this::createMessage);
if (message.isPresent()) {
allMessages.add(message);
break;
}
}
}
//Functional : Not working - Never adds anything to the
//map even when matching status is present
STATUS_MESSAGE.values().stream()
.distinct()
.map(statusMessage -> statusMessage.getStatusAndInfo().keySet())
.flatMap(Collection::stream)
.map(key -> MessageBuilder.createMessage(allStatuses, key, this::createMessage))
.anyMatch(allMessages::add);
The MessageBuilder.createMessage looks like this:
Optional<Status> matchingStatus = statuses.stream()
.filter(matchingStatus::equals)
.findFirst();
System.out.println("Found : " + matchingStatus.toString());
return matchingStatus.flatMap(creator);
Also, for debugging purposes, how can I see what is happening at each step of the stream? The stack in the debugger in intellij wasn't showing anything in the stream.
This should do it:
STATUS_MESSAGE.values().stream()
.distinct()
.forEach(statusMessage ->
statusMessage.getStatusAndInfo().keySet().stream()
.map(status -> MessageBuilder.createMessage(allStatuses, status, this::createMessage))
.filter(Optional::isPresent)
.findFirst()
.ifPresent(allMessages::add)
);
UPDATE
To build the result list using toList instead of adding to a list:
List<Optional<Message>> allMessages = STATUS_MESSAGE.values().stream()
.distinct()
.flatMap(statusMessage ->
statusMessage.getStatusAndInfo().keySet().stream()
.map(status -> MessageBuilder.createMessage(allStatuses, status, this::createMessage))
.filter(Optional::isPresent)
.limit(1)
)
.collect(Collectors.toList());
This should be a comment, but it's too long...
Seems like your MessageBuilder.createMessage method is overcomplicated.
Check below a simplified and more readable version of the same logic:
if (allStatuses.contains(status)) {
System.out.println("Found : " + status.toString());
return creator.apply(status);
}
return Optional.empty();
You should not use forEach for accumulating operations, so this should be more idiomatic:
Function<StatusInfo, Optional<Message>> messageForStatus = statusInfo ->
statusInfo().keySet().stream()
.map(status -> MessageBuilder.createMessage(allStatuses, status, this::createMessage))
.filter(Optional::isPresent)
.findFirst()
.orElse(Optional.empty());
allMessages = STATUS_MESSAGE.values().stream()
.distinct()
.map(StatusMessage::getStatusAndInfo)
.map(messageForStatus)
.filter(Optional::isPresent)
.collect(toList());
As a side note, you have too many optionals, you may want to consider unwrapping some earlier, as a list of optionals may just as well be the list of only the present values.

Get Flux size when Flux is complete

I'm kinda stuck with a trivial task: whenever I query an external API with reactive spring WebClient or query reactive MongoDBRepository, I'd like to log how many entities got through my flux, eg. to log message like "Found n records in the database.". Eg:
return repository.findAll()
.doOnComplete { log.info("Found total n records!") } // how to get the n?
.filter { it.age > 10 }
.distinct { it.name }
TLDR: How to get a flux size (and perhaps it's contents) when it completes?
You can use ConnectableFlux. In your example:
var all = repository.findAll()
.filter { it.age > 10 }
.distinct { it.name }
.publish()
.autoConnect(2)
all.count()
.subscribe {c -> log.info("Found total {} records!", c)}
return all;
By calling the count(). It should emit a Mono when onComplete is observed.
Here was what I did,
AtomicInteger i = new AtomicInteger();
Flux<UserDetails> stringFlux =
Flux.using(() -> stringStream, Flux::fromStream,
Stream::close)
.doOnNext(s -> i.getAndIncrement())
.log()
.map(UserDetails::createUserDetails);
stringFlux
.subscribe(updateUserDetailsService::updateUserDetails);
log.info("number of records: {}", i);

RXJava persist return value of previous flatMap

In RX JAVA(java8), how can I persist value of previous flatMap or map.
public void createAccount(]) {
JsonObject payload = routingContext.getBodyAsJson();
socialService.getOAuthToken(payload)
.flatMap(token -> {
return getAllAccounts(token);
})
.flatMap(accounts -> {
// Save accounts with TOKENS
})
.subscribe(accountID -> {
response(accountID);
);
}
So in above code, in second flatMap how can I get the token from previous flatMap.
You have to zip account and token and pass it to the next Stream operation.
//Note you have to replace T, A with the right type
socialService.getOAuthToken(payload).flatMap(token -> getAllAccounts(token)
.map(account -> new SimpleImmutableEntry<T, A>(token, account)))
.flatMap(accounts -> /* accounts.getKey() -> token, accounts.getValue() -> account */)
.subscribe(accountId -> response(accountId));
Kotlin solution based on the solution by #Flown:
socialService.getOAuthToken(payload)
.flatMap { token ->
getAllAccounts(token)
.map { account -> Pair(token, account) }
}
.flatMap { (token, account) -> /* Use values here */ }
.subscribe { accountId -> response(accountId) }

How to use the result of a thenCompose multiple times in Java 8?

I have a series of thenCompose calls, similar to
myObject.updateDB(payload)
.thenCompose(__ -> getUserID(payload.ID()))
.thenCompose(id -> getProfile(id))
.thenCompose(userProfile -> updateSomething(userProfile))
.thenCompose(__ -> notifyUser(id))
.thenAccept(__ -> doSomething())
.exceptionally(t -> doSomethingElse());
The getUserID call returns a CompletionStage<String> which I use in the next call for getProfile. I need the same id again for the notifyUser call. How to make it available there? The IDE is showing
Cannot resolve symbol id.
The issue with your current code is that by the time you reach .thenCompose(__ -> notifyUser(id)), the variable id is not in scope anymore.
A simple solution in this case would be to invoke multiple thenCompose directly on the CompletionStage returned by getProfile:
myObject.updateDB(payload)
.thenCompose(__ -> getUserID(payload.ID()))
.thenCompose(id ->
getProfile(id)
.thenCompose(userProfile -> updateSomething(userProfile))
.thenCompose(__ -> notifyUser(id))
)
// rest of chain calls
I think, your code becomes simpler, if you don’t insist on using thenCompose for every step:
myObject.updateDB(payload)
.thenCompose(__ -> getUserID(payload.ID()))
.thenAccept(id -> {
updateSomething(getProfile(id).join());
notifyUser(id);
})
.thenRun(() -> doSomething())
.exceptionally(t -> doSomethingElse());
If having each step effectively sequential is your requirement, you can simply use join:
myObject.updateDB(payload)
.thenCompose(__ -> getUserID(payload.ID()))
.thenAccept(id -> {
updateSomething(getProfile(id).join()).join();
notifyUser(id).join();
})
.thenRun(() -> doSomething())
.exceptionally(t -> doSomethingElse());
Considering that the whole chain is effectively sequential, you may just write it straight-forward:
myObject.updateDB(payload)
.thenRun(() -> {
YourUserIDType id = getUserID(payload.ID()).join();
updateSomething(getProfile(id).join()).join();
notifyUser(id).join();
doSomething();
})
.exceptionally(t -> doSomethingElse());

Categories