Reactive Java OneError Resume error handling - project-reactor

Trying to save event has this flow ( the repo is reactive, this is just an example code for testing. I am new reactive, I am using io.projectreactor (3.3))
Validate an event, on failure, write to history
if validate is successful, write event to repo, any failures write to history
if validate fails write to history
inducing some failures to simulate the error condition
import reactor.core.publisher.Mono;
public class MyTest {
static int counter = 0;
public static void main(String args[]) throws InterruptedException
{
String array[] = {"1","2","3","4",null,"5"};
for(int i =0; i < 5; i++)
{
System.out.println("input:: "+array[i]);
new MyTest().createMessage(array[i]);
counter++;
Thread.sleep(500);
}
}
private void createMessage(String input)
{
new MyTest().onMessage(input)
.doOnSuccess(s -> System.out.println("----done::success-----"))
.onErrorResume(e ->
{System.out.println("---done::error --creatMessage::doOnError:: caused by "+e);
return Mono.empty();})
.subscribe();
}
private Mono<String> onMessage(String input)
{
return Mono.create(sink -> {
validate()
.onErrorResume(e -> {
System.out.println("error onMessage:: fail to validate");
sink.error(e);
return Mono.error(e);
})
.flatMap(a -> processObject(input))
.flatMap(h -> {
System.out.println("success onMessage :: save history");
new Service().saveHistory(input, false);
sink.success();
return Mono.just(h);
})
.subscribe();
});
}
private Mono<String> processObject(String input)
{
return Mono.create(sink -> {
new Service().saveEvent(input).flatMap(a -> {
System.out.println("success processObject");
sink.success(a);
return Mono.just(a);
}).onErrorResume(e -> {
new Service().saveHistory(input, true);
System.out.println("error processObject");
sink.error(e);
return Mono.error(e);
}).subscribe();
});
}
private Mono<String> validate()
{
counter++;
return Mono.create(sink -> {
if (counter % 3 == 0)
{
sink.error(new RuntimeException("Validate method error"));
return;
}
sink.success("validate is done ");
return;
});
}
}
Service Class
public class Service {
public Mono<String> saveEvent(String id)
{
return save(id)
.onErrorResume(e -> {
System.out.println("Error in save event");
return Mono.error(e);
}).doOnNext(e -> System.out.println("save event"));
}
public Mono<String> saveHistory(String id, boolean error)
{
return save(id)
.onErrorResume(e -> {
System.out.println("Error in save history");
return Mono.error(e);
}).doOnNext(e -> System.out.println("save history"));
}
public Mono<String> save(String id)
{
if (id == null)
{
throw new RuntimeException("Error saving");
}
return Mono.just("save success");
}
}
I am getting this exception
---done::error --creatMessage::doOnError:: caused by java.lang.RuntimeException: Validate method error
Exception in thread "main" reactor.core.Exceptions$ErrorCallbackNotImplemented: java.lang.RuntimeException: Validate method error
Caused by: java.lang.RuntimeException: Validate method error
at sample.MyTest.lambda$validate$9(MyTest.java:77)
at reactor.core.publisher.MonoCreate.subscribe(MonoCreate.java:57)
at reactor.core.publisher.Mono.subscribe(Mono.java:4110)
at reactor.core.publisher.Mono.subscribeWith(Mono.java:4216)
at reactor.core.publisher.Mono.subscribe(Mono.java:3942)
at sample.MyTest.lambda$onMessage$5(MyTest.java:49)
at reactor.core.publisher.MonoCreate.subscribe(MonoCreate.java:57)
at reactor.core.publisher.Mono.subscribe(Mono.java:4110)
at reactor.core.publisher.Mono.subscribeWith(Mono.java:4216)
at reactor.core.publisher.Mono.subscribe(Mono.java:3942)
at sample.MyTest.createMessage(MyTest.java:30)
at sample.MyTest.main(MyTest.java:18)
Updated working code : based on #Michael Berry comments
public static void main(String args[]) throws InterruptedException
{
String array[] = {"1","2","3","4",null,"5"};
for(int i =0; i < 5; i++)
{
System.out.println("input:: "+array[i]);
new MyTest().createMessage(array[i]);
counter++;
Thread.sleep(500);
}
}
private void createMessage(String input)
{
new MyTest().onMessage(input)
.doOnSuccess(s -> System.out.println("----done::success-----"))
.onErrorResume(e ->
{
System.out.println("---done::error --creatMessage::doOnError:: caused by "+e);
return Mono.empty();
})
.subscribe();
}
private Mono<String> onMessage(String input) {
return validate()
.onErrorResume(e -> {
System.out.println("error onMessage:: fail to validate");
return Mono.error(e);
})
.flatMap(a -> processObject(input))
.flatMap(h -> {
System.out.println("success onMessage :: save history");
new Service().saveHistory(input, false);
return Mono.just(h);
});
}
private Mono<String> processObject(String input)
{
return new Service().saveEvent(input).flatMap(a -> {
System.out.println("success processObject");
return Mono.just(a);
}).onErrorResume(e -> {
new Service().saveHistory(input, true);
System.out.println("error processObject");
return Mono.error(e);
});
}
private Mono<String> validate()
{
counter++;
if (counter % 3 == 0)
{
return Mono.error(new RuntimeException("Validate method error"));
}
return Mono.just("validate is done ");
}
Result
save event
success processObject
success onMessage :: save history
----done::success-----
input:: 2
error onMessage:: fail to validate
---done::error --creatMessage::doOnError:: caused by java.lang.RuntimeException: Validate method error
input:: 3
save event
success processObject
success onMessage :: save history
----done::success-----
input:: 4
save event
success processObject
success onMessage :: save history
----done::success-----
input:: null
error onMessage:: fail to validate
---done::error --creatMessage::doOnError:: caused by java.lang.RuntimeException: Validate method error

You're getting an error here because of your onMessage() implementation, which is a bit bizarre:
You're wrapping a Mono in Mono.create(), which there's no reason to do;
You're subscribing on this inner publisher yourself - that's almost always the wrong thing to do, and won't necessarily do what you expect (subscribing to publishers should be handled by the framework, not your code.) In this case, the key thing is it means that it's treated separately, not part of your reactive chain, so your error handling probably isn't mapping to the inner publisher as you expect;
Your onErrorResume() call on this inner publisher itself returns an error, and there's no other error handling on this inner publisher - hence why that error is unhandled, so it then prints out the stack trace that you're seeing.
Instead, you most likely want your onMessage() method to read thus:
private Mono<String> onMessage(String input) {
return validate()
.onErrorResume(e -> {
System.out.println("error onMessage:: fail to validate");
return Mono.error(e);
})
.flatMap(a -> processObject(input))
.flatMap(h -> {
System.out.println("success onMessage :: save history");
new Service().saveHistory(input, false);
return Mono.just(h);
});
}
...without the Mono.create() (which is only really meant to be used by non-reactor callback APIs for compatibility purposes.) Your output with this change then reads as follows:
input:: 1
save event
success processObject
success onMessage :: save history
----done::success-----
input:: 2
error onMessage:: fail to validate
---done::error --creatMessage::doOnError:: caused by java.lang.RuntimeException: Validate method error
input:: 3
save event
success processObject
success onMessage :: save history
----done::success-----
input:: 4
save event
success processObject
success onMessage :: save history
----done::success-----
input:: null
error onMessage:: fail to validate
---done::error --creatMessage::doOnError:: caused by java.lang.RuntimeException: Validate method error

Related

How to add Channel Handler to TCPServer on Channel initialization

I have the following code snipped that creates a TCPServer, and attaches a ChannelHandler to the channel in the doOnChannelInit() function. The server is to process byte data from an embedded device.
#Component
#RequiredArgsConstructor
public class NettyServer {
Logger log = LoggerFactory.getLogger(NettyServer.class);
private final NettyProperties nettyProperties;
private final NettyServerHandler nettyServerHandler;
private TcpServer server;
public void run() {
server = TcpServer
.create()
.host("localhost")
.port(nettyProperties.getTcpPort())
.doOnChannelInit((connectionObserver, channel, remoteAddress) -> {
log.info("Connection from " + remoteAddress);
channel.pipeline()
.addLast("idleStateHandler", new IdleStateHandler(0, 0, 4, TimeUnit.MINUTES))
.addLast(new ByteArrayDecoder())
.addLast(new ByteArrayEncoder())
.addLast(nettyServerHandler);
});
server.bindNow();
log.info("Server running");
}
}
Channel handler
#Component
#RequiredArgsConstructor
#ChannelHandler.Sharable
public class NettyServerHandler extends SimpleChannelInboundHandler<byte[]> {
Logger log = LoggerFactory.getLogger(NettyServerHandler.class);
private final AttributeKey<byte[]> dataKey = AttributeKey.valueOf("dataBuf");
private final AttributeKey<Integer> dataLen = AttributeKey.valueOf("dataBufLen");
#Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.info("New Meter connection from : " + ctx.channel());
}
#Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
if (ctx.channel() != null) {
log.info(String.format("Meter/Client Disconnected. No: %s ; Channel : %s", meterNo, ctx.channel()));
}
ctx.close();
}
#Override
protected void channelRead0(ChannelHandlerContext ctx, byte[] msg) throws Exception {
log.info("Message received: " + new String(msg);
ctx.channel().read();
}
}
I'm able to connect to the server, but when i send a message, nothing happens, the log statements are not triggered.
I'm not sure what I'm missing here, would appreciate some help.
Thanks
There is no need to add custom handlers to the Netty pipeline. The example above can be written like this:
#Component
#RequiredArgsConstructor
public class NettyServer {
Logger log = LoggerFactory.getLogger(NettyServer.class);
private final NettyProperties nettyProperties;
private TcpServer server;
public void run() {
server = TcpServer
.create()
.host("localhost")
.port(nettyProperties.getTcpPort())
.doOnChannelInit((connectionObserver, channel, remoteAddress) -> {
log.info("Connection from " + remoteAddress);
channel.pipeline()
.addLast("idleStateHandler", new IdleStateHandler(0, 0, 4, TimeUnit.MINUTES));
})
.handle((in, out) ->
in.receive()
.asString()
.doOnNext(s -> log.info("Message received: " + s))
.then());
server.bindNow();
log.info("Server running");
}
}
Consider checking the Reference Documentation
The incoming data can be transformed to String with (asString), to byte[] with (asByteArray) etc. If there is no suitable transformation you can use map(byteBuf -> ...) and transform the ByteBuf to the needed abstraction.

quarkus mutiny: neo4j type mismatch compilation handling

This is my code:
#Path("/hello")
#AllArgsConstructor
public class GreetingResource {
private final Driver driver;
#GET
#Produces(MediaType.TEXT_PLAIN)
public Uni<String> hello() {
return Multi.createFrom().resource(
driver::rxSession,
session -> session.readTransaction(tx -> {
RxResult result = tx.run("MATCH (f:Fruit) RETURN f.name as name ORDER BY f.name");
return Multi.createFrom().publisher(result.records()).map(record -> record.get("name").asString());
})
).withFinalizer(session -> {
return Multi.createFrom().publisher(session.close());
});
}
}
I'm getting those two compilation messages:
Type mismatch: cannot convert from Multi<Object> to Uni<String>
Type mismatch: cannot convert from Multi<Object> to Uni<Void>
I don't quite figure since record.get("name").asString returns me an String...
Any ideas?
The finalizer function must return a Uni<Void>. In your code, it returns a Publisher<Object>. Also, your method is going to return a Multi and not a Uni.
Try the following approach:
#GET
#Produces(MediaType.TEXT_PLAIN)
public Multi<String> hello() {
return Multi.createFrom().resource(
driver::rxSession,
session -> session.readTransaction(tx -> {
RxResult result = tx.run(
"MATCH (f:Fruit) RETURN f.name as name ORDER BY f.name");
return Multi.createFrom().publisher(result.records())
.map(record -> record.get("name").asString());
})
).withFinalizer(session -> {
return Uni.createFrom().publisher(session.close());
});
}

How to return a Mono with a long running task inside map?

I want to transform data inside the map function of the Mono:
long result = 0.0;
return Mono.just(result).map(value -> {
// do some long running transformation here
// and assign it to result (maybe 5 seconds task)
// in our case a request:
Mono<Result> resultObject = service.getResult();
resultObject.subscribe(new Subscriber<Result>() {
#Override
public void onSubscribe(Subscription s) {
System.out.println("subscribe: " + System.currentTimeMillis());
s.request(1);
}
#Override
public void onNext(Result result) {
System.out.println("on next: " + System.currentTimeMillis());
value = result.getValue(); // this is not 0.0
}
#Override
public void onError(Throwable t) {
System.out.println("error " + t);
}
#Override
public void onComplete() {
System.out.println("complete");
}
});
return value;
});
If I call this I always get 0.0 as the result so it is returning before the map function is done. For me that does not make much sense. How else am I supposed to transform my result before returning it?
EDIT
I could do the following but in my opinion this is not an optimal solution:
final CountDownLatch latch = new CountDownLatch(1);
long result = 0.0;
return Mono.just(result).map(value -> {
// do some long running transformation here
// and assign it to result (maybe 5 seconds task)
// in our case a request:
Mono<Result> resultObject = service.getResult();
resultObject.subscribe(new Subscriber<Result>() {
#Override
public void onSubscribe(Subscription s) {
System.out.println("subscribe: " + System.currentTimeMillis());
s.request(1);
}
#Override
public void onNext(Result result) {
System.out.println("on next: " + System.currentTimeMillis());
value = result.getValue(); // this is not 0.0
latch.countDown();
}
#Override
public void onError(Throwable t) {
System.out.println("error " + t);
}
#Override
public void onComplete() {
System.out.println("complete");
}
});
try {
latch.await();
return value;
} catch(InterruptedException e) {
e.printStackTrace();
return -1.0;
}
});
That sounds exactly like what flatMap is for: if your long running task is asynchronous and can be represented as a Publisher<T> then it can be triggered asynchronously by flatMap.
Note that Mono#flatMap(Function) was called Mono#then(Function) in 3.0.x.
So in 3.0.7:
Mono.just(param).then(p -> service.getResult(p));
And in 3.1.0.M3:
Mono.just(param).flatMap(p -> service.getResult(p));
Note that if you don't use the value (service doesn't have a parameter) then you probably can simply provide the continuation Mono, using then(Mono) (which is valid in both 3.0.x and 3.1.x):
Mono.just(paramThatGetsIgnored).then(service.getResult());
(but in that case the starting point of Mono.just(...) isn't terribly relevant)

Does Object result = ((Future<?>) r).get() break spock?

I am working on a legacy grails 2.4.x app.
I wanted to add some extra exception visibility to a class that extends ThreadPoolExecutor
As per
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ThreadPoolExecutor.html#afterExecute-java.lang.Runnable-java.lang.Throwable-
I implemented
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
if (t == null && r instanceof Future<?>) {
try {
Object result = ((Future<?>) r).get();
} catch (CancellationException ce) {
t = ce;
} catch (ExecutionException ee) {
t = ee.getCause();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt(); // ignore/reset
}
}
if (t != null)
System.out.println(t);
}
}
However spock seems to stop running when it hits the Object result = ((Future<?>) r).get() line of code
Is this a know issue?
Something I am doing wrong?
The spock version is:
version='0.7-groovy-2.0'

How to find roles of a user in cloudboost

I am trying to get all the roles assigned to user when user logs in, using the code below.
public class roles extends AsyncTask <String,Void,Void>{
#Override
protected Void doInBackground(String... params) {
final CloudUser user = new CloudUser();
final CloudRole role = new CloudRole("MCA");
user.setUserName(params[0]);
user.setPassword(params[1]);
try {
user.logIn(new CloudUserCallback() {
#Override
public void done(CloudUser cloudUser, CloudException e) throws CloudException {
if (cloudUser != null) {
System.out.println("login Successful");
System.out.println(cloudUser.getUserName());
cloudUser.isInRole(role);
}
if (e != null) {
System.out.println("In logn exception");
e.printStackTrace();
}
}
});
} catch (CloudException e) {
e.printStackTrace();
}
return null;
}
}
I am getting the following error:
FATAL EXCEPTION: AsyncTask #1
Process: com.rakesh_kr.image, PID: 31256
java.lang.RuntimeException: An error occured while executing doInBackground()
at android.os.AsyncTask$3.done(AsyncTask.java:300)
at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:355)
at java.util.concurrent.FutureTask.setException(FutureTask.java:222)
at java.util.concurrent.FutureTask.run(FutureTask.java:242)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at java.lang.Thread.run(Thread.java:818)
Caused by: java.lang.ClassCastException: io.cloudboost.json.JSONArray cannot be cast to java.util.ArrayList
at io.cloudboost.CloudUser.isInRole(CloudUser.java:335)
at com.rakesh_kr.image.MainActivity$roles$1.done(MainActivity.java:174)
at io.cloudboost.CloudUser.logIn(CloudUser.java:219)
at com.rakesh_kr.image.MainActivity$roles.doInBackground(MainActivity.java:168)
at com.rakesh_kr.image.MainActivity$roles.doInBackground(MainActivity.java:155)
at android.os.AsyncTask$2.call(AsyncTask.java:288)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at java.lang.Thread.run(Thread.java:818)
This is a bug that occured in versions of CloudBoost JavaSDK prior to v1.0.7, this has been fixed, please clone the latest sources from github or get the latest jar(1.0.7) which should be available on maven in a few hours from now.

Resources