how to chain reactive Mono(s) in an if-else scenario - kotlin

New to Reactive Java here. Can someone please help with this simple scenario (written in Kotlin):
fun testReactive(): Int {
Mono.just("item-exists-in-database")
.onErrorReturn("item-missing") // if item is not in database an exception will be thrown
.map { item ->
if (item == "item-exists-in-database") {
Mono.just("deleting")
.doOnSuccess { println("deleting item") } // <-- this never prints!
} else {
Mono.just("ok")
}
}.map {
println("Creating item in database")
Mono.just("creating item in database")
}.block()
return 0
}
Running it produces:
Creating item in database
Why doesn't the line "deleting item" ever print?

you could consider the switchIfEmpty operator
https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Mono.html#switchIfEmpty-reactor.core.publisher.Mono-

Related

How to log empty Mono

Lets say I have the following lines
repository.findUser(name = "John Doe")
.map {
// User was found, continue processing
}
.switchIfEmpty(
// Just log that the user was not found
)
My non-best-practice-but-working approach currently is to just put the logging in another function and call it, like switchIfEmpty(userNotFound())
private fun userNotFound(): Mono<out Disposable> {
logger.info { "No user was found." }
return Mono.empty()
}
This works but I cannot imagine this is what I should be doing. How can I improve?
One way is to use the Mono.defer inside switchIfEmpty:
repository.findUser(name = "John Doe")
.switchIfEmpty(Mono.defer{ ... })
Alternatively, you could use doOnSuccess and check if Mono has completed without data(sample in Java):
repository.findUser(name = "John Doe")
.doOnSuccess(e -> {
if (e == null) {
log.info("No user was found.");
}
})

Getting a warning when use objectmapper in flux inappropriate blocking method call in java reactor

i am new to reactor, i tried to create a flux from Iterable. then i want to convert my object into string by using object mapper. then the ide warns a message like this in this part of the code new ObjectMapper().writeValueAsString(event). the message Inappropriate blocking method call. there is no compile error. could you suggest a solution.
Flux.fromIterable(Arrays.asList(new Event(), new Event()))
.flatMap(event -> {
try {
return Mono.just(new ObjectMapper().writeValueAsString(event));
} catch (JsonProcessingException e) {
return Mono.error(e);
}
})
.subscribe(jsonStrin -> {
System.out.println("jsonStrin = " + jsonStrin);
});
I will give you an answer, but I don't pretty sure this is what you want. it seems like block the thread. so then you can't get the exact benefits of reactive if you block the thread. that's why the IDE warns you. you can create the mono with monoSink. like below.
AtomicReference<ObjectMapper> objectMapper = new AtomicReference<>(new ObjectMapper());
Flux.fromIterable(Arrays.asList(new Event(), new Event()))
.flatMap(event -> {
return Mono.create(monoSink -> {
try {
monoSink.success(objectMapper .writeValueAsString(event));
} catch (JsonProcessingException e) {
monoSink.error(e);
}
});
})
.cast(String.class) // this cast will help you to axact data type that you want to continue the pipeline
.subscribe(jsonString -> {
System.out.println("jsonString = " + jsonString);
});
please try out this method and check that error will be gone.
it doesn't matter if objectMapper is be a normal java object as you did. (if you don't change). it is not necessary for your case.
You need to do it like this:
Flux.fromIterable(Arrays.asList(new Event(), new Event()))
.flatMap(event -> {
try {
return Mono.just(new ObjectMapper().writeValueAsString(event));
} catch (JsonProcessingException e) {
return Mono.error(e);
}
})
.subscribe(jsonStrin -> {
System.out.println("jsonStrin = " + jsonStrin);
});

how to only log one line error message not several error message for my code

there're several elements inside configTypeBuilderList, if the value in ruleAttributes not same as the destinationField in ConfigTypeBuilder, it will log the error
ruleCriteriaList.forEach { configRuleCriteria ->
validateConfigTypeBuilder(configRuleCriteria.configTypeBuilderList, ruleAttributesNames)
}
private fun validateConfigTypeBuilder(configTypeBuilderList: List<ConfigTypeBuilder>, ruleAttributes: List<String>) {
val missAttributeList: MutableList<String> = mutableListOf()
configTypeBuilderList.forEach { configTypeBuilder ->
if(configTypeBuilder!= null) {
if (ruleAttributes.firstOrNull { ruleAttribute -> ruleAttribute == configTypeBuilder.destinationField } == null) {
if(!ruleAttributes.contains(configTypeBuilder.destinationField)) {
missAttributeList.add(configTypeBuilder.destinationField)
}
logger.error("{} is wrong", configTypeBuilder.destinationField)
}
}
}
The problem is each time there's only one element(configTypeBuilderList) go into validateConfigTypeBuilder, so the logger shows like this
logger.error("field1 is wrong")
logger.error("field2 is wrong")
...
What I need is, how can I modify my code in order to do this?
logger.error("field1, field2, field3 are wrong")
Edit
I tried the first solution, but I stuck here, I still get the same error result, the reason is because each time there's only one "destinationField", how can I make the list have all the error field, and then log the error, can I use continue or something?
Here are a couple of alternatives:
Add them to a list and log later.
fun foo()
val incorrectItems = mutableListOf<Any>()
// Do some stuff
// on error:
incorrectItems.add(someIncorrectItem)
// Do more stuff
// log the accumulated errors:
logger.error("${incorrectItems.joinToString("")} are wrong")
}
Partition your list into valid and invalid values. Log the invalid ones and process the good ones.
fun foo(someList: List<MyClass>) {
val (goodItems, badItems) = someList.partition { it.isValid() }
// ...where isValid() is whatever code you need to check is OK.
if (badItems.isNotEmpty()) {
logger.error("${badItems.joinToString("")} are wrong")
}
// Do stuff with goodItems
}

How to have Kotlin "Listen" when a function finish executing Successfully

This is my first time using Kotlin, I have to write a simple command-line application where it takes a list of user input strings. Valid inputs are only "Apple" or "Orange" and calculate the price (which is 60 cents and 25 cents respectively). I'm having some trouble with the 3rd requirement
"Build a service that listens for when orders are complete and sends a notification to the customer regarding its status and estimated delivery time. The Mail service subscribes to events from the Orders service and publishes the appropriate event that the customer (you) is able to read from the terminal"
this is what I have done so far
MainApp.tk
import java.util.Scanner
import kotlin.system.exitProcess;
import app.Checkout;
var shopRunning = true;
var applecount = 0;
fun main(args: Array<String>) {
while (shopRunning) {
println("Welcome to Express Store");
println("1. Checkout");
println("2. exit");
var userOption = 0;
//request the user to eneter an option
//if user eneter a options that is not valid it will keep looping til option that is enterd is accepted;
var userSeletedOption = false;
val inputScanner = Scanner(System.`in`);
while (!userSeletedOption) {
print("Select an Option: ");
userOption = inputScanner.nextInt();
//if input entered by the user is not accepted and invaliud message is printed and is promted to enter an option again.
if (userOption != 1 && userOption != 2) {
println("Invalid input detected!");
} else {
userSeletedOption = true;
}
}
if (userOption == 1) {
val checkout = Checkout();
println("We currently have apples and oranges in Stock.")
var list: MutableList<String> = ArrayList();
println(list.size);
var doneAddingToCart = false;
while(!doneAddingToCart){
print("enter name of item to be enter or exit to finish adding to the cart: ")
var item = inputScanner.next();
if(item.equals("exit")){
doneAddingToCart=true;
}
else{
list.add(item);
}
}
if(checkout.verify(list)){ //checks if list has any item that is not an apple or orange
println("Thank you for your Pruchse");
val cost = checkout.Chasher(list)
println("You bought: "+ list.toString());
print("your total is: "+ cost);//returns the total cost
exitProcess(1);//exits from the application
}
} else if (userOption == 2) {
print("Have a great day.");
exitProcess(1);
}
}
}
CheckOut.tk
class Checkout {
//checks if the user entered any invaild items
public fun verify (cart: MutableList<String>) : Boolean{
for(item in cart){
if(!item.equals("Apple") && !item.equals("Orange")){
return false;
}
}
return true;
}
public fun Chasher (cart: MutableList<String>) : Double{
var total = 0.0;
var orangecount = 0;//step 2 offers
var applecount = 0;//step 2 offers
for(item in cart){//step 1 function
if(item.equals("Apple") || item.equals("apple")){
applecount+=1;
total= total + 0.6;
}
if(item.equals("Orange") || item.equals("orange")){
orangecount +=1;
total=total +0.25;
}
}
if(orangecount ==3){//buy three for the price of 2.step 2
println("You qaulidified for our buy 3 oragnes for the price of 2 offer")
total -=0.25;
}
if(applecount ==1){//buy one aple get 1 free. step 2
println("You buy 1 apple get one free")
cart.add("Apple");
}
return total;
}
}
I don't need to send an email just send a message to the command line. Currently, I'm just printing messages (just to see if what I currently have even works). Yeah, I know there many spelling errors, english and writing was never my strongest subject
I can only provide three hints that might help you:
If you exit your program using System.exit, use 0 if the run did not have any problem. (Excerpt from JavaDoc: "The argument serves as a status code; by convention, a nonzero status code indicates abnormal termination.")
For checking equality, simply use == which corresponds to equals in Java. In your special case however, you can use item.equals("apple", ignoreCase=true) or simply item.equals("apple", true).
I'm not sure what the author of your task exactly expects as a solution.
In can imagine you are expected to use lambdas.
An example: Your could refactor your Checkout class like that:
class Checkout {
/**
* Checks if the given [cart] contains only apples and oranges,
* and calls [onSuccess].
* If also other articles are contained, [onSuccess] is not called.
*/
fun verify(cart: List<String>, onSuccess: (List<String>) -> Unit): Unit {
for (item in cart) {
if (!item.equals("apple", true) && !item.equals("Orange", true)) {
return
}
}
onSuccess(cart)
}
}
And then call
val cart = listOf("Orange", "Apple", "apple", "orange")
Checkout().verify(cart, { cart: List<String> ->
println("Thanks you for your purchase: $cart")
})
or even shorter (curly brackets are outside of parenthesis)
Checkout().verify(cart) { cart: List<String> ->
println("Thanks you for your purchase: $cart")
}
What I did here was to extract what is executed if your validation succeeds:
For that, I used a lambda function that accepts a list of articles/strings (List<String>) and returns something I ignore/don't care about -> Unit.
The advantage of that approach is that callers of your verify method can decide what to do on success at their liking because they can pass a lambda function around like any other variable. Here:
val cart = listOf("Orange", "Apple", "apple", "orange")
val onSuccess = { cart: List<String> ->
println("Thanks you for your purchase: $cart")
}
Checkout().verify(cart, onSuccess)
You could also extend Checkout to allow an observer to register.
I deliberately kept the code very simple. Normally you would allow multiple observers to register, only expose what clients are supposed to see and hide the rest, etc.
class Checkout(
val onSuccess : (List<String>) -> Unit
) {
fun verify(cart: List<String>): Unit {
for (item in cart) {
if (!item.equals("apple", true) && !item.equals("Orange", true)) {
return
}
}
onSuccess(cart)
}
}
val checkout = Checkout({ cart: List<String> ->
println("Thanks you for your purchase: $cart")
})
and then
val cart = listOf("Orange", "Apple", "apple", "orange")
checkout.verify(cart)
Be sure to check out https://play.kotlinlang.org/byExample/04_functional/01_Higher-Order%20Functions to learn more about lambda / higher-order functions.

Kotlin: mapNotNull but log what caused null elements

When translating Java to Kotlin code, I encountered the following:
List<Content> getContent(List<Node> nodes, Map<String, Content> content) {
List<Content> result = new ArrayList<>(nodes.size());
for (Node node : nodes) {
Content content = content.get(node.getId());
if (content == null) {
logger.atSevere().log("Content %s was not found", node.getId());
continue;
}
result.add(content);
}
return result;
}
In Kotlin, this can be easily translated if we drop the logger call:
fun getContent(items: List<Node>, content: Map<String, Content): List<Content> {
val contentIds = items.mapNotNull { it.id }
return contentIds.mapNotNull { contentMap[it] }
}
I'm thinking a sequence builder might be nice here. It's also possible to separate out contentIds into two separate collections, one made up of the contentIds that were not present in contentMap, the other made up of the Content mapped to successfully. I bet there is also a better way to get a set of items in a map from a set of keys, but I haven't found the right function.
Please try the following:
fun check(items: List<Node>, content: Map<String, Content?>): List<Content>{
return items.filter{
if (content[it.id] == null){
print("content " + it.id + "was not found")
}
content[it.id] != null
}.map{content[it.id]!!}
}