Project Reactor: Obtain Size of List Contained Within a Mono - spring

I'm trying to do something again here in project reactor that I'm sure is reeeeeal simple for any of you project reactor gurus out there!
I've been searching and scratching around with this one for a while now, and feel I'm once again hitting a wall with this stuff.
All I'm trying to do is determine if a List of objects contained within a Mono is empty or not.
This is what I have so far:
private Mono<Boolean> isLastCardForAccount(String accountId) {
return cardService.getAccountCards(accountId)
.hasElement();
}
I'm thinking the above might work, but I'm having difficulty figuring out how to extract/access the 'Boolean' contained within the returned Mono. I think I have to use 'subscribe' somehow right?
I've mucked around with this stuff for a while now, but still no luck.
Here is how 'getAccountCards' is defined:
public Mono<List<Card>> getAccountCards(final String accountId) {
return cardCrudRepository.getCardsByAccountId(accountId)
.collectList();
}
From CardCrudRepository:
// #Query("SELECT * FROM card WHERE account_id = :accountId") <-Not sure if I need this
Flux<Card> getCardsByAccountId(String accountId);
And lastly, how I'm using 'isLastCardForAccount':
public Mono<Void> updateCardStatus(String accountId, String cardId, String cardStatus) {
return accountService.getAccount(accountId)
.map(Account::getClientId)
.map(clientId -> createUpdateCardStatusServiceRequestData(clientId, cardId, cardStatus))
.flatMap(requestData -> cartaClient.updateCardStatus(requestData)
.then(Mono.defer(() -> isCardBeingCancelled(cardStatus) ? allCardsCancelledForAccount(accountId) ? removeAccount(accountId) :
(isLostOrStolen(cardStatus) ? replaceCard(cardId, cardStatus).flatMap(this::updateCardNumber) : Mono.empty()) : Mono.empty())));
}
As always, any and all help and insight is tremendously appreciated!

I am not sure if this would resolve the issue but this way you can try to write your logic
return accountService.getAccount(accountId)
.map(Account::getClientId)
.map(clientId -> createUpdateCardStatusServiceRequestData(clientId, cardId, cardStatus))
.flatMap(requestData -> cartaClient.updateCardStatus(requestData)
.then(Mono.defer(() ->
Mono.zip(
Mono.just(isCardBeingCancelled(cardStatus)),
isLastCardForAccount(accountId),
Mono.just( isLostOrStolen(cardStatus) )
)
.map(tuple -> {
WRITE YOUR IF ELSE LOGIC
})
The idea is to use zip and then use the tuple for writing logic. The Tuple would be of type Tuple3 of <Boolean, Boolean ,Boolean>. I made the assumption that isLostOrStolen(cardStatus) returns Boolean.

One way of doing that is by using filterWhen operator like this:
.then(Mono.defer(() -> {
if (isCardBeingCancelled(cardStatus)) {
return Mono.just(accountId)
.filterWhen(this::allCardsCancelledForAccount)
.flatMap(this::removeAccount);
} else if (isLostOrStolen(cardStatus)) {
return replaceCard(cardId, cardStatus).flatMap(this::updateCardNumber);
}
return Mono.empty();
}))
You can use filterWhen in the case of asynchronous filtering. Check this section of Which operator do I need? reference and this How to filter Flux asynchronously.
As a side note, this is not going to work as you expect:
private Mono<Boolean> isLastCardForAccount(String accountId) {
return cardService.getAccountCards(accountId)
.hasElement();
}
public Mono<List<Card>> getAccountCards(final String accountId) {
return cardCrudRepository.getCardsByAccountId(accountId)
.collectList();
}
The collectList() will emit an empty List if there is no card. I'd use exists query instead:
public Mono<Boolean> isLastCardForAccount(final String accountId) {
return cardCrudRepository.existsByAccountId(accountId);
}

Related

Reactive way of returning `Map<Long,T>`

I am learning reactive. In an MVC project I have a service method (and the controller has the same signature) like this:
#Override
public Map<Long, Question> getQuestions() {
List<Question> questions = questionRepo.findAllByType(Type.A);
return questions.stream()
.collect(Collectors.toMap(q -> q.getId(), q -> q));
}
Resulting in something similar to this:
{
1: {id: 1, ...}
2: {id: 2, ...}
...
}
Now, switching to reactive and kotlin coroutines. What is the proper way to implement this in a reactive way?
This is the signature of the repository:
interface QuestionRepository : CoroutineCrudRepository<Question, Long> {
#Query("select * from Question q where type = :type")
fun findAllByType(type: Type): Flow<Question>
}
Approaches
From what I think so far using Mono<Map<Long,Question>> seems to make no sense as it would require to block for building the inner map.
Flow<Map<Long,Question>> Does not make sense either, because we do not populate multiple maps.
So my best approach for now is not using a Map...
override fun getQuestions(): Flow<Question> {
return questionRepo.findAllByType(Type.A)
}
...but this would require to change the frontend code (it now needs to convert the list to a map).
I also think of
override fun getQuestions(): Flow<Pair<Long?,Question>> {
return questionRepo.findAllByType(Type.A).map { it.id to it }
}
but this would require the frontend to change as well, because the output would look like
[{"first":1,"second":{"id":1, ...}]
Are there other, better approaches? How would you implement it?
UPDATE
added repository.
Assuming the Flow emits elements one at a time that you want to put into a single Map in the client code, then you can collect them into a MutableMap like this and return it.
suspend fun getQuestions(): Map<Long, Question> {
val map = mutableMapOf<Long, Question>()
questionRepo.findAllByType(Type.A)
.collect {
map[it.id] = it
}
return map
}
If your downstream client code is not expecting a suspend function, I guess you need to wrap this in runBlocking, and presumably the downstream code is already handling the fact that this is a long-running function call.
override fun getQuestions(): Map<Long, Question> = runBlocking {
val map = mutableMapOf<Long, Question>()
questionRepo.findAllByType(Type.A)
.collect {
map[it.id] = it
}
map
}

Merge specifications of different types in Criteria Query Specifications

I'm having an Activity entity which is in #ManyToOne relationship with Event entity and their corresponding metamodels - Activity_ and Event_ were generated by JPA model generator.
I've created specialized classes ActivitySpecifications and EventSpecifications. Those classes contain only static methods whose return Specification. For example:
public interface EventSpecifications {
static Specification<Event> newerThan(LocalDateTime date) {
return (root, cq, cb) -> cb.gt(Event_.date, date);
}
...
}
so when I want to build query matching multiple specifications, I can execute following statement using findAll on JpaSpecificationExecutor<Event> repository.
EventSpecifications.newerThan(date).and(EventSpecifications.somethingElse())
and ActivitySpecifications example:
static Specification<Activity> forActivityStatus(int status) { ... }
How do I use EventSpecifications from ActivitySpecifications ? I mean like merge specifications of different type. I'm sorry, but I don't even know how to ask it properly, but theres simple example:
I want to select all activities with status = :status and where activity.event.date is greater than :date
static Specification<Activity> forStatusAndNewerThan(int status, LocalDateTime date) {
return forActivityStatus(status)
.and((root, cq, cb) -> root.get(Activity_.event) ....
// use EventSpecifications.newerThan(date) somehow up there
}
Is something like this possible?
The closest thing that comes to my mind is using the following:
return forActivityStatus(status)
.and((root, cq, cb) -> cb.isTrue(EventSpecifications.newerThan(date).toPredicate(???, cq, cb));
where ??? requires Root<Event>, but I can only get Path<Event> using root.get(Activity_.event).
In its basic form, specifications are designed to be composable only if they refer to the same root.
However, it shouldn't be too difficult to introduce your own interface which is easily convertible to Specification and which allows for specifications refering to arbitrary entities to be composed.
First, you add the following interface:
#FunctionalInterface
public interface PathSpecification<T> {
default Specification<T> atRoot() {
return this::toPredicate;
}
default <S> Specification<S> atPath(final SetAttribute<S, T> pathAttribute) {
// you'll need a couple more methods like this one for all flavors of attribute types in order to make it fully workable
return (root, query, cb) -> {
return toPredicate(root.join(pathAttribute), query, cb);
};
}
#Nullable
Predicate toPredicate(Path<T> path, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);
}
You then rewrite the specifications as follows:
public class ActivitySpecifications {
public static PathSpecification<Activity> forActivityStatus(ActivityStatus status) {
return (path, query, cb) -> cb.equal(path.get(Activity_.status), cb.literal(status));
}
}
public class EventSpecifications {
public static PathSpecification<Event> newerThan(LocalDateTime date) {
return (path, cq, cb) -> cb.greaterThanOrEqualTo(path.get(Event_.createdDate), date);
}
}
Once you've done that, you should be able to compose specifications in the following manner:
activityRepository.findAll(
forActivityStatus(ActivityStatus.IN_PROGRESS).atRoot()
.and(newerThan(LocalDateTime.of(2019, Month.AUGUST, 1, 0, 0)).atPath(Activity_.events))
)
The above solution has the additional advantage in that specifying WHERE criteria is decoupled from specifying paths, so if you have multiple associations between Activity and Event, you can reuse Event specifications for all of them.
Consider the following :
ClassA {
id;
}
ClassB {
foreignId; //id of A
}
For combining Specification<ClassA> specA, Specification<ClassB> specB
specB = specB.and(combineSpecs(specA);
private static Specification<ClassB> combineSpecs(Specification<ClassA> specA) {
return (root_b,query,builder) {
Subquery<ClassA> sub = query.subquery(ClassA.class);
Root<ClassA> root_a = sub.from(ClassA.class);
Predicate p1 = specA.toPredicate(root_a,query,builder);
Predicate p2 = builder.equal(root_a.get("id"),root_b.get("foreignId"));
Predicate predicate = builder.and(p1,p2);
sub.select(root_a).where(predicate);
return builder.exists(sub);
};
}

How can I use Optional to return empty if there is any exception in the code in it?

Pretty new to Optional/Java8 usage and I had a doubt regarding its usage.
I have a function in my API which returns a String which can be null or empty. Its similar to finding the id in email like: abc#gmail.com -> abc is o/p.
Now one way of doing this was:
public Optional<String> getUser(final String emailId) {
if (TextUtils.isEmpty(emailId)) {
return Optional.empty();
}
String userIDSeparator = "#";
int userNameEndIndex = emailId.indexOf(userIDSeparator);
if (userNameEndIndex == -1) {
return Optional.empty();
}
return Optional.of(emailId.substring(0, userNameEndIndex));
}
I was wondering if there is any neater way of doing this to return an Optional?
Also, Optional was introduced in Java8 so is there anyway the code can be java7 compatible? I am aware of preprocessors in C not sure if something similar is available.
Note:
I have not compiled this code, so there might be some errors. I wanted the input on Optional.
Thanks!
Well, the code can certainly be reduced. i.e.
public Optional<String> getUser(final String emailId) {
return Optional.of(emailId)
.filter(email -> email.contains("#"))
.map(email -> email.substring(0, email.indexOf("#")));
}
if this method can ever receive a null value then you'd need to change Optional.of(emailId) to Optional.ofNullable(emailId).
As for:
Also, Optional was introduced in Java8 so is there any way the code can
be java7 compatible?
Not that I know of. There may be other libraries that have similar functionality to Java's Optional type, so a little bit of research online may get you to the right place.
Maybe you mean something like this :
public Optional<String> getUser(final String emailId) {
return Optional.ofNullable(emailId)
.filter(email -> email.contains("#"))
.map(email -> Optional.of(email.replaceAll("(.*?)#.*", "$1")))
.orElseGet(Optional::empty);
}
Example
null -> Optional.empty
"" -> Optional.empty
"abc#gmail.com" -> abd
As #Aominè mention, there are some unnecessary parts in my solution, you can use this version instead :
public Optional<String> getUser(final String emailId) {
return Optional.ofNullable(emailId)
.filter(email -> email.contains("#"))
.map(email -> email.replaceAll("(.*?)#.*", "$1"));
}

Is there a way to print out the chain of all operations in a Flux?

Given a Flux or a Mono from project reactor is a there a way to get the Flux or Mono to print out what the operator chain looks like. For example given the code below.
Fulx flux = Flux.just("a","b","c")
.map( v -> v.toUpperCase())
.log();
Is there some way to get the flux to print out a list of all the operators that are chained inside in the processing pipeline? Some nice ascii formatted text or a marble diagram?
printTheFlux(flux) should make a nice printout that show the structure of all the operators from the example above. I am not expecting to produce the code in the lambda's just a way to see what operators are chained together.
There is partial building blocks for doing this with the Scannable interface:
public String textRepresentation(Flux<?> flux) {
Scannable sc = Scannable.from(flux);
//scan the last operator in the chain and ask if it knows its parents
List<String> names = sc.parents().map(Scannable::operatorName)
.collect(Collectors.toList());
//as it traverses the chain from bottom to top, we need to reverse the order
Collections.reverse(names);
//need to also add the last operator
names.add(sc.operatorName());
return names.toString();
}
#Test
public void textRepresentationTest() {
Flux flux = Flux.just("a","b","c")
.map( v -> v.toUpperCase())
.log();
System.out.println(textRepresentation(flux));
}
Prints
[map, log]
Not all operators fully support it though (as you can see, the just source doesn't for instance).
Nice suggestion!
However, waiting for it, we can just have something like :
Disposable flux = Flux.just("a", "b", "c")
.map(String::toUpperCase)
.doOnNext(FluxUtil::print)
.subscribe();
Where FluxUtil::print is just a static method that you can write with different ways.
Here is the complete code works for me:
public class FluxUtil {
private static String s = "";
public static void main(String[] args) {
Disposable flux = Flux.just("a", "b", "c")
.map(String::toUpperCase)
.doOnNext(FluxUtil::print)
.subscribe();
}
private static Object print(Object o) {
s = !s.isEmpty() ? s.concat("->") : s;
s = s.concat(o.toString());
System.out.println(s);
return o;
}
}

How to check if Mono is empty?

I'm developing a app with Spring Boot 2.0 and Kotlin using the WebFlux framework.
I want to check if a user id exits before save a transaction. I'm stucked in a simple thing like validate if a Mono is empty.
fun createTransaction(serverRequest: ServerRequest) : Mono<ServerResponse> {
val transaction = serverRequest.body(BodyExtractors.toMono(Transaction::class.java))
transaction.flatMap {
val user = userRepository.findById(it.userId)
// If it's empty, return badRequest()
}
return transaction.flatMap { transactionRepository.save(it).then(created(URI.create("/transaction/" + it.id)).build()) }
}
It is possible to do what I want?
The techniques that allow checking whether Flux/Mono is empty
Using operators .switchIfEmpty/.defaultIfEmpty/Mono.repeatWhenEmpty
Using mentioned operators you will be able to react to the case when Stream has been completed without emitting any elements.
First of all, remember that operators such .map, .flatMap, .filter and many others will not be invoked at all if there no onNext has been invoked.
That means that in your case next code
transaction.flatMap {
val user = userRepository.findById(it.userId)
// If it's empty, return badRequest()
}
return transaction.flatMap { transactionRepository.save(it).then(created(URI.create("/transaction/" + it.id)).build()) }
will not be invoked at all, if transaction will be empty.
In case if there is a requirement for handling cases when your flow is empty, you should consider operators like next in the following manner:
transaction
.flatMap(it -> {
val user = userRepository.findById(it.userId)
})
.swithIfEmpty(Flux.defer(() -> Flux.just(badRequest())));
Actual solution
Also, I have noted that you created two sub-flows from the main transaction. Actually, following code will not be executed at all:
transaction.flatMap {
val user = userRepository.findById(it.userId)
// If it's empty, return badRequest()
}
and will be only executed the last one, which is returned from the method. That happens because you ain't subscribed using operator .subscribe(...).
The second point, you can't subscribe to the same request body more the one time (kind of limitation for WebClient's reponse). Thus you are required to share your request body in the next way, so completed example will be:
fun createTransaction(serverRequest: ServerRequest): Mono<ServerResponse> {
val transaction = serverRequest.body(BodyExtractors.toMono(Transaction::class.java)).cache()
transaction
.flatMap { userRepository.findById(it.userId) }
.flatMap { transaction.flatMap { transactionRepository.save(it) } }
.flatMap { ServerResponse.created(URI.create("/transaction/" + it.id)).build() }
.switchIfEmpty(transaction.flatMap { ServerResponse.badRequest().syncBody("missed User for transaction " + it.id) })
}
Or more simple case without sharing transaction flow but using Tuple:
fun createTransaction(serverRequest: ServerRequest): Mono<ServerResponse> {
val emptyUser = !User()
val transaction = serverRequest.body<Mono<Transaction>>(BodyExtractors.toMono(Transaction::class.java))
transaction
.flatMap { t ->
userRepository.findById(t.userId)
.map { Tuples.of(t, it) }
.defaultIfEmpty(Tuples.of(t, emptyUser))
}
.flatMap {
if (it.t2 != emptyUser) {
transactionRepository.save(it.t1)
.flatMap { ServerResponse.created(URI.create("/transaction/" + it.id)).build() }
} else {
ServerResponse.badRequest().syncBody("missed User for transaction " + it.t1.id)
}
}
}
You can check it using the Mono's provided method hasElement() which is analogous to Optional's isPresent(). The method definition is :
Mono<Boolean> hasElement()
for more details checkout : project reactor documentation
In case you have to perform some action based on this value you can further use switchIfEmpty() to provide with alternate publisher.
Let me start by saying I am a newbie on reactive (java) and on this forum.
I think you cannot really check in this code if a mono is empty because a mono represents code that will be executed later on, so in this code body you won't know yet if its is empty. Does that make sense?
I just wrote something similar in Java which seems to work (but not 100% this is the best approach either):
public Mono<ServerResponse> queryStore(ServerRequest request) {
Optional<String> postalCode = request.queryParam("postalCode");
Mono<ServerResponse> badQuery = ServerResponse.badRequest().build();
Mono<ServerResponse> notFound = ServerResponse.notFound().build();
if (!postalCode.isPresent()) { return badQuery; }
Flux<Store> stores = this.repository
.getNearByStores(postalCode.get(), 5);
return ServerResponse.ok().contentType(APPLICATION_JSON)
.body(stores, Store.class)
.switchIfEmpty(notFound);
}
We can use switchIfEmpty method for this
Below example, I'm checking if the user exists with email if not then add it
userRepository.findByEmail(user.getEmail())
.switchIfEmpty(s -> {
user.setStatus("InActive");
String encodedPassword = DigestUtils.sha256Hex(user.getPassword());
user.setPassword(encodedPassword);
userRepository.save(user).subscribe();
s.onComplete();
}).then(Mono.just(user));
Use Mono with Optional:
return findExistingUserMono
.map(Optional::of)
.defaultIfEmpty(Optional.empty())
.flatMap(optionalUser -> {
if(optionalUser.isPresent()) {
return Mono.error('xxxx');
}
return this.userService.create(optionalUser.get());
});
This way it will always emit Optional value so that the stream will never break.

Resources