Mono::zip doesn't short-circuit for some kind of 'Mono' objects - spring-webflux

I try to zip two Monos into one, like in the following example.
final Mono<String> mono1 = ...;
final Mono<String> mono2 = ...;
final Mono<String> result = Mono.zip(mono1, mono2)
.map(args -> args.getT1() + ":" + args.getT1());
My expectation now is, that mono2 is not evaluated if mono1 is an error-Mono, but this is not always the case. It seems that mono2 is evaluated, even in the case of an error, if it is the result of a Mono::flatMap operation. The following test will reproduce this behaviour.
#Test
public void shortCircuitZip() {
final var count = new AtomicInteger();
// Short-circuting in zip if first is an error-mono.
final var mono1 = Mono.zip(
Mono.<String>error(new IllegalArgumentException("Illegal")),
Mono.fromSupplier(() -> {
count.incrementAndGet();
return "RESULT";
})
)
.map(args -> "%s:%s".formatted(args.getT1(), args.getT2()));
assertThatThrownBy(mono1::block).isInstanceOf(IllegalArgumentException.class);
assertThat(count.get()).isEqualTo(0);
// If the second mono has a 'flatMap' in it, the zip is no longer
// short-circuting.
final var mono2 = Mono.zip(
Mono.<String>error(new IllegalArgumentException("Illegal")),
Mono.fromSupplier(() -> {
count.incrementAndGet();
return "RESULT";
})
.flatMap(v -> Mono.just(v))
)
.map(args -> "%s:%s".formatted(args.getT1(), args.getT2()));
assertThatThrownBy(mono2::block).isInstanceOf(IllegalArgumentException.class);
// This assertion fails with a "flatMapped" second mono.
assertThat(count.get()).isEqualTo(0);
}
The question is, whether this behaviour is intentional or a bug in the reactor library?
I'm using the following reactor version: io.projectreactor:reactor-core:3.4.19

Related

How to extract value from MonoNext (and convert to byte[])

Map<String,Mono<byte[]>> map = new HashMap<>();
List<User> userList = new ArrayList<>();
map.entrySet().stream().forEach(entry -> {
if (entry.getValue() == null) {
log.info("Data not found for key {} ", entry.getKey());
} else {
entry.getValue().log().map(value -> {
try {
return User.parseFrom(value);
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
return null;
}).log().subscribe(p -> userList.add(p));
}
here entry.getValue() => MonoNext
parseFrom(accepts byte[])
I am new to reactive programming world, How to resolve this MonoNext to values it actually have, tried using flatMap instead but that also didnot work
Any suggestion appreciated !! Thanks in advance !!
MonoNext (an internal Reactor implementation of Mono) emits the value asynchronously, which means that it might not have yet the value when evaluated in your code. The only way to retrieve the value is to subscribe to it (either manually or as part of a Reactor pipeline using flatMap and others) and wait until the Mono emits its item.
Here is what your code would look like if placed in a Reactor pipeline using flatMap:
Map<String, Mono<byte[]>> map = new HashMap<>();
List<User> userList = Flux.fromIterable(map.entrySet())
.filter(entry -> entry.getValue() != null)
.doOnDiscard(Map.Entry.class, entry -> log.info("Data not found for key {} ", entry.getKey()))
.flatMap(entry -> entry.getValue()
.log()
.map(User::parseFrom)
.onErrorResume(error -> Mono.fromRunnable(error::printStackTrace)))
.collectList()
.block();
Note that the block operator will wait until all items are retrieved. If you want to stay asynchronous, you can remove the block and return a Mono<List<User>>, or also remove the collectList to return a Flux<User>.

WebFlux & formation DTO

Hello recently started studying Webflux.
And sometimes I encounter the tasks that you need to form a simple DTO and return it
Take for example the usual class dto
#Data
#Builder
public static class Dto {
private long id;
private String code1;
private String code2;
}
And a primitive service with two methods...
#Nullable Mono<String> getCode1(long id);
#Nullable String getCode2(long id);
And wrote a method that forms at the output of Mono
private Mono<Dto> fill(long id) {
var dto = Dto.builder()
.id(id)
.build();
//doOnNext
var dtoMono1 = service.getCode1(id)
.map(code -> {
dto.setCode1(code);
return dto;
})
.doOnNext(innerDto -> innerDto.setCode2(service.getCode2(id)));
//map
var dtoMono2 = service.getCode1(id)
.map(code -> {
dto.setCode1(code);
return dto;
})
.map(unused -> service.getCode2(id))
.map(code -> {
dto.setCode1(code);
return dto;
});
//just
var dtoMono3 = Mono.just(dto)
.flatMap(innerDto -> service.getCode1(innerDto.getId()));
//just
var dtoMono4 = Mono.fromCallable(() -> dto)
.subscribeOn(Schedulers.boundedElastic())
.flatMap(innerDto -> service.getCode1(innerDto.getId()));
}
QUESTION:
Is it possible to simply create DTO and use it in the Webflux call
chain ... Or I need to wrap it in mono.just or mono.fromcallable
(what are the pros and cons)
How best to fill in values via doOnNext
or through MAP. An extra line (Return DTO) appears in the case of
MAP and some people also told me if for example NULL comes, doOnNext will
miss it and go further to fill up current dto. But on the other, the MAP is used
to transform the object, and the doOnNext is more for debugging and
logging
Thanks you...
How about using zip operator in such a case?
I hope this example can help you:
private Mono<Dto> fill(long id) {
return Mono.zip(someService.getCode1(id), Mono.just(someService.getCode2(id)))
.map(tuple ->
Dto.builder()
.id(id)
.code1(tuple.getT1())
.code2(tuple.getT2())
.build()
);
}

Thymeleaf StringTemplateResolver with flux doesn't work

My configuration is here
#Configuration
class TemplateConfiguration {
#Bean
fun templateResolver(): StringTemplateResolver? {
val templateResolver = StringTemplateResolver()
templateResolver.templateMode = TemplateMode.TEXT
return templateResolver
}
#Bean
#Primary
fun templateEngine(): SpringWebFluxTemplateEngine {
var engine = SpringWebFluxTemplateEngine()
engine.setTemplateResolver(templateResolver())
return engine
}
}
and
var books = Flux.fromArray(arrayOf(Book( ....), Book(....))
var context = Context()
var streamData = ReactiveDataDriverContextVariable(books, 10)
context.setVariable("books", streamData)
// then something like below
engine.process(template, context)
Error message is:
org.thymeleaf.templateparser.text.TextParseException: Exception evaluating SpringEL expression: "book.price" (template: "[# th:each="book: ${books}"] - [(${book.price})] [/]"
......
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'price' cannot be found on object of type 'org.thymeleaf.spring5.context.webflux.ReactiveDataDriverContextVariable' - maybe not public or not valid?
......
I think it's a problem that the Flux Stream was not delivered properly.
How can i solve this?
Plz help me and I apologize in advance for the poor of my English.
I'm not sure this is a solution (maybe is a workaround??)
I use collectList().map instead of ReactiveDataDriverContextVariable
then it worked
var books = Flux.fromArray(arrayOf(Book( ....), Book(....))
var context = Context()
books.collectList()
.map { list ->
context.setVariable("books", list)
engine.process(template, context)
}
It's a some kind of monologue ^^;;;

How to rewrite removed "forEachInvocation" jmockit?

I have to upgrade from jmockit v0.999.15 to jmockit v1.33 and I am having issues rewriting a test which is using "forEachInvocation"
request.addParam(anyString, anyString); minTimes = 1; maxTimes = 10;
forEachInvocation = new Object() {
void validate(String someName, String someValue) {
if(Utils.XML.equals(someName)) {
assertTrue("incorrect value",someValue.contains("This is a test"));
}
}
};
The above piece of code is part of Expectations.
In jmockit website the following is mentioned, but I am not sure how to rewrite it:
"Version 1.7: Removed the forEachInvocation field, which was deprecated in release 1.6. Existing tests using it should instead take advantage of the withCapture() and withCapture(List) methods, or convert the handler object to a Delegate object assigned to the result field (in the case of a recorded expectation)."
It would be something like this:
List<String> someNames = new ArrayList<>();
List<String> someValues = new ArrayList<>();
request.addParam(withCapture(someNames), withCapture(someValues));minTimes = 1;maxTimes = 10;
for (String someName : someNames) {
// assert some name as/if needed
}
for (String someValue; someValues) {
// assert some values as/if needed
}

Mono.CSharp: how do I inject a value/entity *into* a script?

Just came across the latest build of Mono.CSharp and love the promise it offers.
Was able to get the following all worked out:
namespace XAct.Spikes.Duo
{
class Program
{
static void Main(string[] args)
{
CompilerSettings compilerSettings = new CompilerSettings();
compilerSettings.LoadDefaultReferences = true;
Report report = new Report(new Mono.CSharp.ConsoleReportPrinter());
Mono.CSharp.Evaluator e;
e= new Evaluator(compilerSettings, report);
//IMPORTANT:This has to be put before you include references to any assemblies
//our you;ll get a stream of errors:
e.Run("using System;");
//IMPORTANT:You have to reference the assemblies your code references...
//...including this one:
e.Run("using XAct.Spikes.Duo;");
//Go crazy -- although that takes time:
//foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
//{
// e.ReferenceAssembly(assembly);
//}
//More appropriate in most cases:
e.ReferenceAssembly((typeof(A).Assembly));
//Exception due to no semicolon
//e.Run("var a = 1+3");
//Doesn't set anything:
//e.Run("a = 1+3;");
//Works:
//e.ReferenceAssembly(typeof(A).Assembly);
e.Run("var a = 1+3;");
e.Run("A x = new A{Name=\"Joe\"};");
var a = e.Evaluate("a;");
var x = e.Evaluate("x;");
//Not extremely useful:
string check = e.GetVars();
//Note that you have to type it:
Console.WriteLine(((A) x).Name);
e = new Evaluator(compilerSettings, report);
var b = e.Evaluate("a;");
}
}
public class A
{
public string Name { get; set; }
}
}
And that was fun...can create a variable in the script's scope, and export the value.
There's just one last thing to figure out... how can I get a value in (eg, a domain entity that I want to apply a Rule script on), without using a static (am thinking of using this in a web app)?
I've seen the use compiled delegates -- but that was for the previous version of Mono.CSharp, and it doesn't seem to work any longer.
Anybody have a suggestion on how to do this with the current version?
Thanks very much.
References:
* Injecting a variable into the Mono.CSharp.Evaluator (runtime compiling a LINQ query from string)
* http://naveensrinivasan.com/tag/mono/
I know it's almost 9 years later, but I think I found a viable solution to inject local variables. It is using a static variable but can still be used by multiple evaluators without collision.
You can use a static Dictionary<string, object> which holds the reference to be injected. Let's say we are doing all this from within our class CsharpConsole:
public class CsharpConsole {
public static Dictionary<string, object> InjectionRepository {get; set; } = new Dictionary<string, object>();
}
The idea is to temporarily place the value in there with a GUID as key so there won't be any conflict between multiple evaluator instances. To inject do this:
public void InjectLocal(string name, object value, string type=null) {
var id = Guid.NewGuid().ToString();
InjectionRepository[id] = value;
type = type ?? value.GetType().FullName;
// note for generic or nested types value.GetType().FullName won't return a compilable type string, so you have to set the type parameter manually
var success = _evaluator.Run($"var {name} = ({type})MyNamespace.CsharpConsole.InjectionRepository[\"{id}\"];");
// clean it up to avoid memory leak
InjectionRepository.Remove(id);
}
Also for accessing local variables there is a workaround using Reflection so you can have a nice [] accessor with get and set:
public object this[string variable]
{
get
{
FieldInfo fieldInfo = typeof(Evaluator).GetField("fields", BindingFlags.NonPublic | BindingFlags.Instance);
if (fieldInfo != null)
{
var fields = fieldInfo.GetValue(_evaluator) as Dictionary<string, Tuple<FieldSpec, FieldInfo>>;
if (fields != null)
{
if (fields.TryGetValue(variable, out var tuple) && tuple != null)
{
var value = tuple.Item2.GetValue(_evaluator);
return value;
}
}
}
return null;
}
set
{
InjectLocal(variable, value);
}
}
Using this trick, you can even inject delegates and functions that your evaluated code can call from within the script. For instance, I inject a print function which my code can call to ouput something to the gui console window:
public delegate void PrintFunc(params object[] o);
public void puts(params object[] o)
{
// call the OnPrint event to redirect the output to gui console
if (OnPrint!=null)
OnPrint(string.Join("", o.Select(x => (x ?? "null").ToString() + "\n").ToArray()));
}
This puts function can now be easily injected like this:
InjectLocal("puts", (PrintFunc)puts, "CsInterpreter2.PrintFunc");
And just be called from within your scripts:
puts(new object[] { "hello", "world!" });
Note, there is also a native function print but it directly writes to STDOUT and redirecting individual output from multiple console windows is not possible.