Flatmap throws compile time error while flattened nested Mono - spring

Have below Method
private Mono<EventSlotBook> getTestEventSlotBook(EventUserAppt eventUserAppt){
Query query = new Query();
query.addCriteria(
new Criteria().andOperator(
Criteria.where("eventId").is(eventUserAppt.getEventId()),
Criteria.where("eventConfigId").is(eventUserAppt.getEventConfigId()),
Criteria.where("eventSlotId").is(eventUserAppt.getEventSlotId()),
Criteria.where("appointmentDate").in(eventUserAppt.getAppointmentDate()
)));
return this.reactiveMongoTemplate.findOne(query, EventSlotBook.class)
.flatMap(eventSlotExistingEntity -> {
if(eventSlotExistingEntity.getEventUsers() != null) {
eventSlotExistingEntity.getEventUsers().add(eventUserAppt.getEventUser());
}
return Mono.just(eventSlotExistingEntity);
})
.switchIfEmpty(getInitialTestEventSlotBook(eventUserAppt));
}
And above method called by
public Mono<EventSlotBookRequestDto> saveEventSlotBookFinal(Mono<EventSlotBookRequestDto> eventSlotBookRequestDtoMono){
log.info("Start::SaveEventSlotBook#######Final");
Mono<EventSlotBookRequestDto> eventDtoSaved =
eventSlotBookRequestDtoMono.map(AppUtils::dtoToEventUserApptEntity)
.flatMap(eventUserApptEntity -> getEventUserAppt(eventUserApptEntity))
.doOnNext(eventUserApptBeforeSave -> {
log.info("####BeforeSave::{}",eventUserApptBeforeSave);
})
.flatMap(eventUserApptRepository::save)
.doOnNext( eventUserApptAftereSave -> {
log.info("####AfterSave::{}",eventUserApptAftereSave);
})
.map(eventUserApptAfterSave -> getTestEventSlotBook(eventUserApptAfterSave)) -> IDE shows it returns Mono<Mono<EventSlotBoo>>
.flatMap(eventSlotBookrepository::save) --> Compile time error: o instance(s) of type variable(s) exist so that Mono<EventSlotBook> conforms to EventSlotBook
.map(eventSlotBooEntity -> AppUtils.entityToDto((EventSlotBook)eventSlotBooEntity));
log.info("End::SaveEventSlotBook#####Final");
return eventDtoSaved;
}
#Repository
public interface EventSlotBookRepository extends ReactiveMongoRepository<EventSlotBook,String> {
}
Not sure why .flatMap(eventSlotBookrepository::save) --> Compile time error: o instance(s) of type variable(s) exist so that Mono conforms to EventSlotBook it throws this error. flatMap expected flattened Mono<Mono> to EventSlotBook and save this data

ReactiveMongoRepository does not have a save method which would accept a Mono. It can only accept an instance of the entity type, so the following would work:
.flatMap(eventUserApptAfterSave -> getTestEventSlotBook(eventUserApptAfterSave)) // map changed to flatMap
.flatMap(eventSlotBookrepository::save)

Related

call private method (PowerMockito Test)

First a save() method is executed which passes the test until it reaches a condition, where if it is true it calls the saveBankAccountAndRole() method and if it is false it sends a Mono.error(new Exception("...").
The sizeAccounts(String customerId) method does pass the test.
In the saveBankAccountAndRole(BankAccountDto bnkDto) method, after executing the sizeAccounts() method, the test does not pass, what am I missing?
public Flux<BankAccountDto> findAccountsByCustomerId(String customerId) {
return mongoRepository
.findAll()
.filter(ba ->
ba.getCustomerId().equals(customerId))
.map(AppUtils::entityToDto);
}
private Mono<Long> sizeAccounts(String customerId){
return findAccountsByCustomerId(customerId)
.count();
}
private Mono<BankAccountDto> saveBankAccountAndRole(BankAccountDto bnkDto) {
return sizeAccounts(bnkDto.getCustomerId())
.flatMap(number -> {
bnkDto.setOpeningOrder(number + 1);
return mongoRepository.save(AppUtils.dtoToEntity(bnkDto))
.switchIfEmpty(Mono.error(new Exception("Problem saving the bank account")))
.zipWhen(bnk -> {
var customerRoleDto = new CustomerRoleDto();
customerRoleDto.setBankAccountId(bnk.getBankAccountId());
customerRoleDto.setCustomerId(bnkDto.getCustomerId());
customerRoleDto.setRoleType(bnkDto.getRoleType());
return webClientRoleHelper.saveCustomerRole(customerRoleDto);
})
.switchIfEmpty(Mono.error(new Exception("Problem saving roles")))
.map(tuple -> AppUtils.entityToDto(tuple.getT1()));
});
}
test:
#Mock
private IMongoBankAccountRepository mongoRepository;
#InjectMocks
private BankAccountServiceImpl bankAccountServiceImpl;
#Test
void saveBankAccountAndRoleTest() throws Exception {
when(mongoRepository.findAll()).thenReturn(Flux.just(bnkDto)
.map(AppUtils::dtoToEntity));
when(mongoRepository.findAll().filter(ba ->
ba.getCustomerId().equals(customerId)))
.thenReturn(Flux.just(bnkDto).map(AppUtils::dtoToEntity));
StepVerifier.create(bankAccountServiceImpl.findAccountsByCustomerId(customerId))
.expectSubscription()
.expectNext(bnkDto)
.verifyComplete();
var spy = PowerMockito.spy(bankAccountServiceImpl);
PowerMockito.when(spy, "sizeAccounts", customerId)
.thenReturn(Mono.just(2L));
PowerMockito.when(spy, "saveBankAccountAndRole",bnkDto)
.thenReturn(Mono.just(bnkDto));
}
exception:
java.lang.AssertionError: expectation "expectNext(com.nttdata.bootcamp.model.dto.BankAccountDto#147c4523)" failed (expected value: com.nttdata.bootcamp.model.dto.BankAccountDto#147c4523; actual value: com.nttdata.bootcamp.model.dto.BankAccountDto#551725e4) at com.nttdata.bootcamp.business.impl.BankAccountServiceImplTest.saveBankAccountAndRoleTest(BankAccountServiceImplTest.java:267)
Which sends me when verifyComplete()
By looking at the code in your test, you shouldn't expect an specific object to be returned.
when(mongoRepository.findAll()).thenReturn(Flux.just(bnkDto)
.map(AppUtils::dtoToEntity));
when(mongoRepository.findAll().filter(ba ->
ba.getCustomerId().equals(customerId)))
.thenReturn(Flux.just(bnkDto).map(AppUtils::dtoToEntity));
The code above is mapping that DTO object to an Entity, which makes sense for a repository. However, that means that the following piece of code will "remap" it to a newly created object:
.zipWhen(bnk -> {
var customerRoleDto = new CustomerRoleDto();
customerRoleDto.setBankAccountId(bnk.getBankAccountId());
customerRoleDto.setCustomerId(bnkDto.getCustomerId());
customerRoleDto.setRoleType(bnkDto.getRoleType());
return webClientRoleHelper.saveCustomerRole(customerRoleDto);
})
Thus, you should be expecting an object with that same class, containing the same instance's variables values. But you can't expect it to be the exact same object.
You might want to try this (untested code):
StepVerifier.create(bankAccountServiceImpl.findAccountsByCustomerId(customerId))
.expectSubscription()
.expectMatches(dto -> dto.getBankAccountId().equals(bankDto.getBankAccountId) && dto.getCustomerId.equals(bnkDto.getCustomerId))
.verifyComplete();
I hope that works out for you.

Project Reactor: Obtain Size of List Contained Within a Mono

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);
}

Map directly from a mono of List of objects to mono of list of DTOs

public Mono<List<AdvancesDto>> findBySearchKeys(Map<String, String> paramsMap) {
List<AdvancesDto> advDTOList= new ArrayList<>();
return advanceRepo.findAllByKeys(
tpNumber, vCode, arrivalNumber)
.collectList()
.flatMap(document -> {
AdvancesDto aa=document.get(0).dto();
advDTOList.add(aa);
if(document.get(1)!=null) {
aa = document.get(1).dto();
advDTOList.add(aa);
}
return Mono.just(advDTOList);
})
.switchIfEmpty(Mono.error(new DataException(HttpStatus.NOT_FOUND, "Record/s Not Found")))
.doOnError(error -> new DataException(HttpStatus.BAD_REQUEST, "Problem while fetching Data", error));
}
DB is returning Mono of List of Advances which I'm converting into Mono of List of AdvancesDTO and returning to caller.
My Advances.java already has a builder method called dto() which converts the Advances object directly to AdvancesDTO
I want the code inside flatMap method to do direct mapping from Advances.java to AdvancesDTO.java, the current code doesn't look good and might give index out of bounds exception
I think you have mixed things up in your code. If you DB is returning Advances that provides a method to convert it to AdvancesDTO, change your code to:
public Mono<List<AdvancesDto>> findBySearchKeys(Map<String, String> paramsMap) {
return advanceRepo.findAllByKeys(
tpNumber, vCode, arrivalNumber)
.map(advances -> advances.dto()) //transfer Advances to AdvancesDTO
.collectList() //Collect them in a list.
.switchIfEmpty(Mono.error(new DataException(HttpStatus.NOT_FOUND, "Record/s Not Found")))
.doOnError(error -> new DataException(HttpStatus.BAD_REQUEST, "Problem while fetching Data", error));
}
If you see your code:
public Mono<List<AdvancesDto>> findBySearchKeys(Map<String, String> paramsMap) {
List<AdvancesDto> advDTOList= new ArrayList<>(); //Don't do this. collectList does this already.
return advanceRepo.findAllByKeys(
tpNumber, vCode, arrivalNumber)
.collectList() //Don't collect firts, transfer the data first then collect.
.flatMap(document -> {
AdvancesDto aa=document.get(0).dto(); // This is also wrong, document is in reality a List<Advances>, this is not a good name at all.
advDTOList.add(aa);
if(document.get(1)!=null) { // Only the first two elements? And if there is more?
aa = document.get(1).dto();
advDTOList.add(aa);
}
return Mono.just(advDTOList);
})
.switchIfEmpty(Mono.error(new DataException(HttpStatus.NOT_FOUND, "Record/s Not Found")))
.doOnError(error -> new DataException(HttpStatus.BAD_REQUEST, "Problem while fetching Data", error));
}
you can use BeanUtils.copyproperties(sourceObject, targetObject)

Use Method that returns an Int inside a Stream

I have a simple method :
public int getPrice(String bookingName)
{
//return the price of a booking
}
I also have the class :
public class Booking
{
String name;
...
}
I want to group the bookings in a map(key = name of the booking, value = getPrice(bookingName)) so I did :
public TreeMap<String, Integer> bookingForName() {
return bookings.stream().
collect(Collectors.groupingBy(Booking::getName,Collectors.summingInt(getPrice(Booking::getName))));
}
This doesnt' work it says :
Multiple markers at this line:
- The target type of this expression must be a functional interface
- The method getPrice(String) in the type Manager is not applicable for the arguments `(Booking::getName)`
How can I do?
Thanks!
Your getPrice() method takes a String, not a functional interface, so you can't call getPrice(Booking::getName), and even if you could, summingInt doesn't accept an int.
Change:
Collectors.summingInt(getPrice(Booking::getName))
to:
Collectors.summingInt(b -> getPrice(b.getName()))
Also note that Collectors.groupingBy returns a Map, not a TreeMap. If you must have a TreeMap, you should call a different variant of groupingBy.
public TreeMap<String, Integer> bookingForName() {
return bookings.stream()
.collect(Collectors.groupingBy(Booking::getName,
TreeMap::new,
Collectors.summingInt(b -> getPrice(b.getName()))));
}

Java 8 Stream dynamic filter field of a class

I have a class with properties that have their getter and setter each.
I load a list of this class with values from a DB, and I need to create a function that can make a filter over this stream calling different method from the class.
Example:
listActionfilter.stream()
.filter(u -> u.getAccion().toUpperCase().trim().contains(accion))
.collect(Collectors.toList());
I need to do is this:
function xxx('**methodtosearch**', '**valuetosearch**') {
listActionfilter.stream()
.filter(u -> u.('**methodtosearch**')
.toUpperCase().trim().contains('**valuetosearch**'))
.collect(Collectors.toList());
}
Is this possible?
Your function could have the following signature (assuming the object is an ListAction object...
public List<ListAction> function(Predicate<ListAction> predicate) {
return listActionfilter.stream()
.filter(predicate)
.collect(Collectors.toList());
}
And call it the following way
function(u -> u.getAccion().toUpperCase().trim().contains(accion));
Assuming that all your target methods returns String, you can use this :
public List<Action> xxx(Function<Action, String> methodSelector, String valueToMatch) {
return listActionfilter.stream()
.filter(t -> methodSelector.apply(t).toUpperCase().trim(). contains(valueToMatch))
.collect(Collectors.toList());
}
You can invoke the method like this :
List<Action> list1 = xxx(Accion::method1, "value1")
List<Action> list2 = xxx(Accion::method2, "value2")

Resources