Spring Integration subflow depending on .handle method - java

I am using spring integration to change a flow after particular retry is completed. My IntegrationFlow bean for errorResponse is as follows:
#Bean
public IntegrationFlow errorMailResponse(#Qualifier(ERROR_CHANNEL) PollableChannel errorChannel) {
return IntegrationFlows.from(errorChannel)
.handle(MessagingException.class, (payload, headers) -> handleMessageException(payload),
e -> e.poller(p -> p.fixedDelay(pollerInterval)))
.channel(NO_OUTPUT_CHANNEL)
.get();
}
If method handleMessageException returns an object I want the flow to continue to particular channel - MAIN_EVENTS_CHANNEL if handleMessageException returns null I want to continue to NO_OUTPUT_CHANNEL.
Is that possible to achieve with Spring integration? I tried to use subflow but I am not sure if it is the way. https://docs.spring.io/spring-integration/reference/html/dsl.html#java-dsl-subflows
#Bean
public IntegrationFlow errorMailResponse(#Qualifier(ERROR_CHANNEL) PollableChannel errorChannel) {
return IntegrationFlows.from(errorChannel)
.handle(MessagingException.class, (payload, headers) -> handleMailMessageException(payload),
e -> e.poller(p -> p.fixedDelay(pollerInterval)))
.publishSubscribeChannel(subscription -> subscription
.subscribe(subflow -> subflow
.<MailPojo>handle((payload, headers) -> {
// if if result handleMessageException == null
})
.channel(NO_OUTPUT_CHANNEL))
.subscribe(subflow -> subflow
.<MailPojo>handle((payload, headers) -> {
// if result handleMessageException !=null
})
.channel(MAIN_EVENTS_CHANNEL)))
.get();
}
Any help is appreciated.

First of all the null is not a payload. Therefore messaging does not support null in most cases. The integration flow just stops at the point where you return null: https://docs.spring.io/spring-integration/docs/current/reference/html/messaging-endpoints.html#service-activator-namespace
The service activator is one of those components that is not required to produce a reply message. If your method returns null or has a void return type, the service activator exits after the method invocation, without any signals.
So, your assumption to make a logical decision is not correct with Spring Integration. You need to think about something what could be used as a signal for such a NO_OUTPUT_CHANNEL. You can create an artificial NullType and use a PayloadTypeRouter to determine when to go next according the payload type returned from your handleMailMessageException():
.<Object, Class<?>>route(Object::getClass, m -> m
.channelMapping(MailPojo.class, MAIN_EVENTS_CHANNEL)
.channelMapping(NullType.class, NO_OUTPUT_CHANNEL))
Another way is to use an Optional and check its content in the router function. Either way you need to use a router.

Related

Spring Webflux - "scanAvailable": true

I'm using a RouterFunction to define endpoints in my Spring Boot application. My service returns a Mono<Object> and I want to return the result of this when the endpoint is called. I also need to authenticate so I pass a UserPrinciple object through.
Router
#Bean
RouterFunction<ServerResponse> router() {
return route()
.GET("/api/endpoint-name", this::getExample)
.build();
}
private Mono<ServerResponse> getExample(ServerRequest request) {
return ServerResponse.ok().body(fromPublisher(getUserPrincipal().map(service::getSomething), Object.class)).log();
}
private Mono<UserPrincipal> getUserPrincipal() {
return ReactiveSecurityContextHolder.getContext()
.map(ctx -> ctx.getAuthentication())
.map(auth -> auth.getPrincipal())
.map(UserPrincipal.class::cast);
}
Service
public Mono<Object> getSomething(UserPrincipal userPrincipal) {
WebClient webClient = getWebClient(userPrincipal.getJwt());
return webClient.get()
.uri(uriBuilder -> uriBuilder.path("another/server/endpoint").build())
.retrieve()
.bodyToMono(Object.class);
}
The endpoint is returning this:
{
"scanAvailable": true
}
which suggests that I'm passing the Mono into the body of the response instead of passing in the result. However I've used fromPublisher which I thought would resolve this.
I can't find any examples where the service returns a Mono and the route correctly returns the result of the Mono.
How can I correctly pass a Mono/Flux as the body of the response?
im not going to explain the difference between mapand flatMapsince i have already written a quite comprehensive explanation here:
Do you have a test to show differences between the reactor map() and flatMap()?
The problem in the above code is the return of Object. And input parameters of Object into certain functions. The first function is pretty straight forward
Mono<UserPrincipal> = getUserPrincipal();
While the second one gets a bit more hairy:
Mono<Mono<Object> value = getUserPrincipal().map(service::getSomething);
So why are we getting A nested Mono?, well the get something returns a Mono<Object> and the Map return according the the api is Mono<R> where R is what we return from getSomething.
We then stick it into the fromPublisher which will unrap the first Mono ending up trying to serialize the Mono<Object>resulting in the strange response.
{
"scanAvailable": true
}
The answer here is pay more close attention to the type system. The body function takes a Publisher (Mono or Flux) so you don't need the fromPublisher function.
And also changing map to flatMap since the return type from inside a flatMap is a publisher.
ServerResponse.ok()
.body(getUserPrincipal()
.flatMap(service::getSomething), Object.class));

How subscribe in Webflux without using it?

I'm using WebFlux, and I want to log with AOP as follow:
Class LogAop {
#AfterReturning("execution ...")
public void configSetted() {
// Getting username by token
Mono<Sting> username = ReactiveSecurityContextHolder.getContext()
.map { ... };
username.subscribe ({it -> loggerin.info(it);});
}
}
In the above code, I want to log username, but there is no log. How can I subscribe to Mono or Flux without returning to the method?
Note: Some times I want to do differnt things on subscribe data, such as saving data in db.
This should work unless ReactiveSecurityContextHolder.getContext() doesn't emit any item.
Mono<String> username = ReactiveSecurityContextHolder.getContext()
.map { ... };
username.subscribe (it -> loggerin.info(it));
I'd suggest adding a log to find out:
username.log().subscribe (it -> loggerin.info(it));

Spring Cloud Stream Reactive Listener Without Output

I'm using Reactive Spring Cloud Stream and I'm having trouble creating a StreamListener without an Output. The following code works as long as no malformed messages are received. When a malformed message is received, the flux closes.
#StreamListener
public void handleMessage(#Input(MessagingConfig.INPUT) Flux<String> payloads) {
payloads.flatMap(objectToSave -> reactiveMongoTemplate.insert(objectToSave)).subscribe();
}
If I understand correctly, it is preferable to let the framework subscribe to the flux instead of subscribing to it manually. This isn't a problem when a listener has an output, because I can simply return the flux like so:
#StreamListener
#Output(MessagingConfig.OUTPUT)
public Flux<String> handleMessage(#Input(MessagingConfig.INPUT) Flux<String> payloads) {
return payloads.flatMap(objectToSave -> reactiveMongoTemplate.insert(objectToSave));
}
The framework seems to handle bad messages in a way that doesn't close the flux when it is returned. Is there any way to let the framework handle the flux when the listener doesn't specify an output?
Consider switching to using Spring Cloud Function (SCF) programming model which we have recently adopted.
Basically, as long as you have the latest code base (2.1.0.RC4 is the latest and RELEASE is few days away) you're fine. Here is the example of your code using SCF programming model:
#SpringBootApplication
#EnableBinding(Sink.class)
public class SampleReactiveConsumer {
public static void main(String[] args) {
SpringApplication.run(SampleReactiveConsumer.class,
"--spring.cloud.stream.function.definition=consume");
}
#Bean
public Consumer<Flux<String>> consume(){
return payloads -> payloads.flatMap(objectToSave -> reactiveMongoTemplate.insert(objectToSave)).subscribe();
}
}
You can also remove reactive module from your classpath as we're also considering deprecating it all together
If you really want to avoid SCF mentioned in Oleg's answer you could try below, hacky approach.
const val IN = "input"
const val OUT = "dummy-output"
interface Channels {
#Input(IN)
fun input(): MessageChannel
#Output(OUT)
fun output(): MessageChannel
}
#EnableBinding(Channels::class)
class MsgList {
#StreamListener
#Output(OUT)
fun receive(#Input(IN) messages: Flux<String>): Flux<Void> {
return messages
.doOnNext { if (it == "err") throw IllegalStateException("err") }
.doOnNext { println(it) }
.flatMap { Mono.empty<Void>() }
}
}
Output binding will be created but no messages will go through. In case of RabbitMQ that means - dummy exchange will appear, but queue won't get created.
Also errors would be handled as you expected. With above example, you may send 3 messages, "ok", "err", "ok2", and you will see "ok", then exception, then "ok2" on the screen. An "ok2" and any subsequent valid message will be handled properly.

updating object with spring data mongodb and kotlin is not working

I have the following request handler
fun x(req: ServerRequest) = req.toMono()
.flatMap {
...
val oldest = myRepository.findOldest(...) // this is the object I want to modify
...
val v= anotherMongoReactiveRepository.save(Y(...)) // this saves successfully
myRepository.save(oldest.copy(
remaining = (oldest.remaining - 1)
)) // this is not saved
ok().body(...)
}
and the following mongodb reactive repository
#Repository
interface MyRepository : ReactiveMongoRepository<X, String>, ... {
}
The problem is that after the save() method is executed there is no changed in the object. I managed to fix the problem with save().block() but I don't know why the first save on the other repository works and this one isn't. Why is this block() required?
Nothing happens until someone subscribes to reactive Publisher. That's why it started to work when you used block(). If you need to make a call to DB and use the result in another DB request than use Mono / Flux operators like map(), flatMap(),... to build a pipeline of all the operations you need and after that return resulting Mono / Flux as controller’s response. Spring will subscribe to that Mono / Flux and will return the request. You don't need to block it. And it is not recommended to do it (to use block() method).
Short example how to work with MongoDB reactive repositories in Java:
#GetMapping("/users")
public Mono<User> getPopulation() {
return userRepository.findOldest()
.flatMap(user -> { // process the response from DB
user.setTheOldest(true);
return userRepository.save(user);
})
.map(user -> {...}); // another processing
}

Nested webflux routing always goes with the route on the top

Can anyone tell me in this example of routes everytime I type ex: /api/person/1, etc it all goes to /api/person? No matter which method I choose, it always goes with /api/person.
#Bean
public RouterFunction<ServerResponse> monoRouterFunction(PersonService personService) {
return RouterFunctions
.nest(path("/api/person"),
route(method(GET), personService::findAllPeople)
.andRoute(GET("/{id}"), personService::findOnePerson)
.andRoute(POST("/add"), personService::addPerson)
.andRoute(PUT("/update"), personService::updatePerson)
.andRoute(DELETE("/delete/{id}"), personService::deletePerson));
}
Below code is working fine. I have personally tried in my local. As #Brain told, just add GET("/") for findAllPeople() handler method.
#Bean
public RouterFunction<ServerResponse> monoRouterFunction(PersonService personService)
{
return RouterFunctions
.nest(path("/api/person"),
route(method(GET("/")), personService::findAllPeople)
.andRoute(GET("/{id}"), personService::findOnePerson)
.andRoute(POST("/add"), personService::addPerson)
.andRoute(PUT("/update"), personService::updatePerson)
.andRoute(DELETE("/delete/{id}"), personService::deletePerson));
}
Sample working application: https://github.com/karthikaiselvan/spring-reactive-mongo
Unlike the annotation model, WebFlux.fn is very explicit about routing: ordering and all predicates matter. But the good thing is it's easier to debug and you can set debug points in your predicates to understand why a request is being routed to a handler.
In this case, this RouterFunction could be described as:
If the path starts with "/api/person"
and the method is GET -> then personService::findAllPeople
and the method is POST and path matches "/api/person/{id}" -> then personService::findOnePerson
etc
Because the first match wins, a request like "GET /api/person/42" will match 1), since it starts with "/api/person" and it is a GET request.
If you want to change that, you can either change the order of your routes, or change your predicate for route(GET("/"), personService::findAllPeople).
even we can remove static imports, besides if we have different types of media types we can use the requestPredicate like the below code :
RouterFunction<ServerResponse> json = route()
.nest(accept(APPLICATION_JSON), builder -> builder
.GET("/{id}", personHandler::findOnePerson)
.GET("", personHandler::findAllPeople)).build();
RouterFunction<ServerResponse> html = route()
.nest(accept(TEXT_HTML), builder -> builder
.GET("/{id}", personHandler::renderPerson)
.GET("", personHandler::renderPersons)).build();
return route()
.path("api/person", () -> html.and(json)) // the default would be the first one(here is html)
.build();
you can find more details in my repository: https://github.com/minarashidi/webflux

Categories