Mono<Object> being returned instead of Mono<ResponseEntity> when mapping (Java 8) - java-8

Trying to practice reactive coding for an API but I'm struggling to understand what I'm doing wrong when using flatMap() and map() to cast to a ResponseEntity object. The error mentions that the code is returning a Mono<Object> and cant be cast/transformed into a Mono<ResponseEntity<>>.
Public Mono<ResponseEntity<?> deleteEndpoint(String someId) {
return db.existsById(someId).flatMap(exists -> {
if (!exists) return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
else {
Mono<Boolean> deletedStuff1 = someFunct();
Mono<Boolean> deletedStuff2 = anotherFunct();
Mono<Tuple2<Boolean, Boolean>> deletedStuff = Mono.zip(deletedStuff1, deletedStuff2);
return deletedStuff.then(Mono.just(ResponseEntity.status(NO_CONTENT).build());
}
});
}
All help is appreciated

From .flatMap() you must return Publisher, not actual object
In this if statement you return ResponseEntity instead of Mono<ResponseEntity>
So, just wrap it with Mono
if (!exists) {
return Mono.just(ResponseEntity.status(HttpStatus.BAD_REQUEST).build());
} else {
// ...

Related

return Mono.error inside Map Spring Webflux

For Matching condition, return Mono.error from code , but gives compilation error. I commented out
//return Mono.error(new RuntimeException("User Phone Exists already"));
Result:
Compilation Error: Required Type Mono<EventSlotBook>, Provided
Mono<Object>
Code:
public Mono<EventSlotBook> getEventSlotBookWithAppointmentId(EventSlotBook eventSlotBookEntity) {
Query query = new Query();
query.addCriteria(
new Criteria().andOperator(
Criteria.where("eventId").is(eventSlotBookEntity.getEventId()),
Criteria.where("eventConfigId").is(eventSlotBookEntity.getEventConfigId()),
Criteria.where("eventSlotId").is(eventSlotBookEntity.getEventSlotId())));
return this.reactiveMongoTemplate.findOne(query, EventSlotBook.class)
.map(eventSlotBookEntityFromDb -> {
EventSlotBook eventSlotNewEntity = eventSlotBookEntityFromDb.toBuilder().build();
if(eventSlotNewEntity.getEventUsers() != null) {
for(EventUser eventUserIno:eventSlotNewEntity.getEventUsers()) {
if(eventUserIno.getPhoneNumber().equalsIgnoreCase(eventSlotBookEntity.getEventUser().getPhoneNumber())){
//return Mono.error(new RuntimeException("User Phone Exists already"));
}
}
}
int maxTokenVal = Integer.valueOf(eventSlotNewEntity.getMaxTokenVal()).intValue() + 1;
EventUser eventUser = new EventUser(eventSlotNewEntity.getEventUser().getName(),eventSlotNewEntity.getEventUser().getPhoneNumber(),String.valueOf(maxTokenVal));
eventSlotNewEntity.getEventUsers().add(eventUser);
eventSlotNewEntity.setMaxTokenVal(String.valueOf(maxTokenVal));
eventSlotNewEntity.setEventUser(eventUser);
return eventSlotNewEntity;
//return Mono.error(new RuntimeException("Ts"));
})
.switchIfEmpty(getEventSlotBook(eventSlotBookEntity));
}
caller of Method : I should handle Mono.error and return to rest API that user already exists ?. Please help on this
public Mono<EventSlotBookRequestDto> saveEventSlotBook(Mono<EventSlotBookRequestDto> eventSlotBookRequestDtoMono){
log.info("Start::SaveEventSlotBook");
Mono<EventSlotBookRequestDto> eventDtoSaved =
eventSlotBookRequestDtoMono.map(AppUtils::dtoToEntity)
.flatMap(eventSlotEntity -> getEventSlotBookWithAppointmentId(eventSlotEntity))
.doOnNext(eventSlotEntityBeforeSave -> {
log.info("####BeforeSave::{}",eventSlotEntityBeforeSave);
})
.flatMap(eventSlotBookrepository::save)
.doOnNext( eventSlotBookAfterSave -> {
log.info("####AfterSave::{}",eventSlotBookAfterSave);
})
.map(AppUtils::entityToDto);
log.info("End::SaveEventSlotBook");
return eventDtoSaved;
}
map is used to apply a synchronous function to each item, therefore you can't return Mono from it. To return an error from map you could just throw an exception and error signal will be emited. As an alternative you you use handle operator and use SynchronousSink to emit next or error signal.
But in your case you need to use flatMap instead because saveEventSlotBook returns Mono and should be transformed asynchronously.

How to return 404 when return type if MappingJacksonValue from #RestController

I have the following Spring #RestController method:
public MappingJacksonValue getUserView(User viewer, Long vieweeid) {
User viewee = getUser(vieweeid);
if(viewee == null) throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "entity not found"
);
final MappingJacksonValue result = new MappingJacksonValue(viewee);
Class view = Views.Minimal.class;
if(viewer.getId().equals(viewee.getId())) {
view = Views.Full.class;
}
...some lines omitted...
result.setSerializationView(view);
return result;
}
Although this is working fine, I am not happy with throwing an exception to return a 404 if the entity requested is not found.
What is the best practice to handle this?
If you like, you can make your method return ResponseEntity<MappingJacksonValue> and then return ResponseEntity.notFound().build():
public ResponseEntity<MappingJacksonValue> getUserView(User viewer, Long vieweeid) {
User viewee = getUser(vieweeid);
if(viewee == null) return ResponseEntity.notFound().build();
final MappingJacksonValue result = new MappingJacksonValue(viewee);
Class view = Views.Minimal.class;
if(viewer.getId().equals(viewee.getId())) {
view = Views.Full.class;
}
...some lines omitted...
result.setSerializationView(view);
return ResponseEntity.ok(result);
}

How to define dependencies on two client calls in quarkus reactive programming

I have two Client APIs that return an Uni.
Uni<Customer> getCustomer(customerID)
Uni<Address> getAddress(addressID)
And I want to open a REST API
Uni<FullCustomer> getFullCustomer(String customerID)
The logic is to make the Customer Client call first. If the returned customer object has addressID then make the second Address Client call and get shipping address details. If shipping address is not available then just wrap the customer in FullCustomer object and return else wrap both customer and address in FullCustomer object and return.
I dont want to block the thread on client call (await().indefinitely()), hence i am using onItem and transfer method call. But my code returns a Uni<Uni> and i want it to return a Uni.
#GET
#Path("api/customer/{id}")
#Produces({ "application/json" })
Uni<Uni<FullCustomer>> getFullCustomer(#PathParam("id") String customerID){
Uni<Customer> customerResponse = getCustomer(customerID);
Uni<Uni<FullCustomer>> asyncResponse = customerResponse.onItem().transform(customer -> {
if (customer.getAddressId() != null) {
Uni<Address> addressResponse = getAddress(customer.getAddressId());
Uni<FullCustomer> fullCustomer = addressResponse.onItem().transform(address -> {
if (address.getShippingAddress() != null) {
return new FullCustomer(customer, address.getShippingAddress());
} else {
return new FullCustomer(customer);
}
});
}
return Uni.createFrom().item(new FullCustomer(customer));
});
return asyncResponse;
}
How can I rewrite my code so that it returns Uni keeping reactive ( async client ) calls
Got the solution. Thanks Ladicek for comments.
public Uni<FullCustomer> getFullCustomer(#PathParam("id") String customerID) {
return getCustomer(customerID)
.onItem()
.transformToUni(customer -> {
if (customer.getAddressId() != null) {
return getAddress(customer.getAddressId()).onItem().transform(address -> {
if (address.getShippingAddress() != null) {
return new FullCustomer(customer, address.getShippingAddress());
} else {
return new FullCustomer(customer);
}
});
} else {
return Uni.createFrom().item(new FullCustomer(customer));
}
});
}

How to make HATEOAS render empty embedded array

Usually CollectionModel will return an _embedded array, but in this example:
#GetMapping("/{id}/productMaterials")
public ResponseEntity<?> getProductMaterials(#PathVariable Integer id) {
Optional<Material> optionalMaterial = materialRepository.findById(id);
if (optionalMaterial.isPresent()) {
List<ProductMaterial> productMaterials = optionalMaterial.get().getProductMaterials();
CollectionModel<ProductMaterialModel> productMaterialModels =
new ProductMaterialModelAssembler(ProductMaterialController.class, ProductMaterialModel.class).
toCollectionModel(productMaterials);
return ResponseEntity.ok().body(productMaterialModels);
}
return ResponseEntity.badRequest().body("no such material");
}
if the productMaterials is empty CollectionModel will not render the _embedded array which will break the client. Is there any ways to fix this?
if (optionalMaterial.isPresent()) {
List<ProductMaterial> productMaterials = optionalMaterial.get().getProductMaterials();
CollectionModel<ProductMaterialModel> productMaterialModels =
new ProductMaterialModelAssembler(ProductMaterialController.class, ProductMaterialModel.class).
toCollectionModel(productMaterials);
if(productMaterialModels.isEmpty()) {
EmbeddedWrappers wrappers = new EmbeddedWrappers(false);
EmbeddedWrapper wrapper = wrappers.emptyCollectionOf(ProductMaterialModel.class);
Resources<Object> resources = new Resources<>(Arrays.asList(wrapper));
return ResponseEntity.ok(new Resources<>(resources));
} else {
return ResponseEntity.ok().body(productMaterialModels);
}
}

How to return an object from Mono SpringWebFlux more effective?

I am using Spring Webflux, and I need to return the object of Author from Mono<Book>, But I don’t know how to do it more correctly.
The method that should return Author:
private Author getRegistredAuthor(Book bookInput) {
String fullName = bookInput.getAuthor().getFullName();
Optional<Author> optionalAuthor = repository.findByFullName(fullName).blockOptional();
if(optionalAuthor.isPresent()){
return optionalAuthor.get();
}else {
Author author = Author.builder().fullName(fullName).build();
return repository.insert(author).block();
}
}
The method that uses Author to create a new entity:
public Mono<BookDto> create(#RequestBody Book book) {
book.setAuthor(getRegistredAuthor(book));
return bookRepository.insert(book)
.map(BookDto::of);
}
As I understand it, if I use block(), I will greatly reduce the effectiveness of reactive.
Will it be more effective if I use so ?
return repository.insert(author).blockOptional().get();
How would it be most efficient to return an object of Author?
This should do..
private Mono<Author> getRegistredAuthor(String fullName) {
Mono<Author> optionalAuthor = repository.findByFullName(fullName); // .blockOptional();
optionalAuthor
.switchIfEmpty({
Author author = Author.builder().fullName(fullName).build();
return repository.insert(author).
})
.flatMap(author -> author);
}
As a result, I solved my problem using Mono.zip. Maybe my solution will help someone too:
#PostMapping("/api/book")
public Mono<BookDto> create(#RequestBody Book book) {
return getObjectMono(book).flatMap(b -> bookRepository.insert(book))
.map(BookDto::of);
}
private Mono<Object> getObjectMono(Book book) {
return Mono.zip(
getRegistredAuthor(book),
getRegistredGenre(book)
).map(objects -> {
book.setAuthor(objects.getT1());
book.setGenre(objects.getT2());
return book;
});
}
private Mono<Genre> getRegistredGenre(Book bookInput) {
String genreTitle = bookInput.getGenre().getTitle();
return genreRepository.findByTitle(genreTitle)
.switchIfEmpty(
genreRepository.insert(Genre.builder().title(genreTitle).build())
);
}
private Mono<Author> getRegistredAuthor(Book bookInput) {
String fullName = bookInput.getAuthor().getFullName();
return authorRepository.findByFullName(fullName)
.switchIfEmpty(
authorRepository.insert(Author.builder().fullName(fullName).build())
);
}

Resources