Spring Integration inbound-gateway reply-channel has no subscribers for channel - java

I have a trivial "Hello World" example workflow here where I want to expose an inbound-gateway web service that responds in plain text. I believe the way I route the response to myReplyChannel is incorrect.
<int:channel id="myRequestChannel"/>
<int:channel id="myReplyChannel"/>
<int-http:inbound-gateway id="myGateway"
path="/hi"
supported-methods="GET"
request-channel="myRequestChannel"
reply-channel="myReplyChannel"/>
<int:transformer input-channel="myRequestChannel"
output-channel="myReplyChannel"
expression="'Hello World!'"/>
This works when deployed, but when I first call the service I see this logged:
Adding {bridge:null} as a subscriber to the 'myReplyChannel' channel
Channel 'org.springframework.web.context.WebApplicationContext:myReplyChannel' has 1 subscriber(s).
started org.springframework.integration.endpoint.EventDrivenConsumer#4eef7503
Looks like Spring is adding in a subscriber for myReplyChannel at the last minute. I'd prefer to do it correctly myself.
Unit Test
I wrote a simple unit test to debug this..
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:hello.xml" })
public class HelloWorldTest {
#Autowired
private MessageChannel myRequestChannel;
#Test
public void test() {
myRequestChannel.send(MessageBuilder.withPayload("").build());
}
}
This errors out with:
org.springframework.messaging.MessageDeliveryException:
Dispatcher has no subscribers for channel
'org.springframework.context.support.GenericApplicationContext#64485a47.myReplyChannel'.
This reads to me like my configuration is wrong and here Spring isn't holding my hand.
Alternative Configuration:
I've tried just dropping myReplyChannel all together and it worked without anything in the logs.
<int:channel id="myRequestChannel"/>
<int-http:inbound-gateway id="myGateway"
path="/ok"
supported-methods="GET"
request-channel="myRequestChannel"/>
<int:transformer input-channel="myRequestChannel" expression="'OK'"/>
Is this the correct setup? If so, what is the reply-channel parameter for?
With this configuration, I get the following error in my unit test:
org.springframework.messaging.MessagingException:
org.springframework.messaging.core.DestinationResolutionException:
no output-channel or replyChannel header available

Adding {bridge:null} as a subscriber to the 'myReplyChannel' channel
.
debug this
There's nothing to "debug". This is just a DEBUG message from framework internals. Each request gets a dedicated replyChannelheader. Typically, you don't need a reply-channel on the gateway; the framework will automatically route to this request's reply channel header when it arrives at some component that has no output-channel (as you have found in your second test).
If you do specify a reply channel, the gateway creates a bridge internally so that any reply specifically sent there is bridged to the request's replyChannel header.
Typically, the only reason to ever specify a reply-channel is if you want do do something else with the reply (e.g. wire tap the channel to log the reply, or make the channel a publish-subscribe channel so you can send a copy of the reply somewhere else).
Your tests are failing because you are not populating the replyChannel header like the gateway does.
If you want to emulate the HTTP gateway in your test code, use a messaging gateway, or simply use a MessagingTemplate.convertSendAndReceive() - either one will correctly set up the replyChannel header in the request message.
Alternatively, use:
myRequestChannel.send(MessageBuilder.withPayload("")
.setReplyChannel(new QueueChannel())
.build());
Each request needs its own reply channel header so we know how to route the reply to the right requesting thread.

Related

Convert Spring Integration Cafe demo from xml config to Java 8 DSL

I try to convert the integration cafe demo to Java 8 DSL, there are some existing examples in the official Spring Integration Samples.
I want to combine all the good parts of these examples.
Java 8 DSL instead of XML
AMQP to slipt the workflow in smaller flows.
Using Jackson to handle the payload in JSON.
The codes(not work as expected, some issues) are here.
In the official XML samples, it is easy to set up a request/reply channel in Amqp gateway. But go to Java 8 DSL, the options are missing.
And when the application, it will complain "reply channel or output channel is required".
BTW, is there any better option to debug/unit testing the Spring integration application?
Update 1: confused when I was writing the flow of coldDrinks flow.
The XML is from the original cafe-amqp project.
<!-- To receive an AMQP Message from a Queue, and respond to its reply-to address, configure an inbound-gateway. -->
<int-amqp:inbound-gateway
id="coldDrinksBarista"
request-channel="coldJsonDrinks"
queue-names="cold-drinks"
connection-factory="rabbitConnectionFactory" />
<int:chain input-channel="coldJsonDrinks">
<int:json-to-object-transformer type="org.springframework.integration.samples.cafe.OrderItem"/>
<int:service-activator method="prepareColdDrink">
<bean class="org.springframework.integration.samples.cafe.xml.Barista"/>
</int:service-activator>
<int:object-to-json-transformer content-type="text/x-json"/>
</int:chain>
How to convert this into Java DSL effectively. I added my thoughts inline
#Bean
public IntegrationFlow coldDrinksFlow(AmqpTemplate amqpTemplate) {
return IntegrationFlows
.from("coldDrinks")
.handle(
Amqp.outboundGateway(amqpTemplate)
.exchangeName(TOPIC_EXCHANGE_CAFE_DRINKS)
.routingKey(ROUTING_KEY_COLD_DRINKS)
)
.log("coldDrinksFlow")
.channel(preparedDrinksChannel())
.get();
}
#Bean
public IntegrationFlow coldDrinksBaristaFlow(ConnectionFactory connectionFactory, Barista barista) {
return IntegrationFlows
.from(Amqp.inboundGateway(connectionFactory, QUEUE_COLD_DRINKS)
.configureContainer(
c -> c.receiveTimeout(10000)
)// If setup replyChannel the below `handle` is not worked as expected.
)
.handle(OrderItem.class, (payload, headers) -> (Drink) barista.prepareColdDrink(payload))
//If adding a channel here, the flow will NOT return back the `coldDrinksFlow` will cause another exception, "its requiresReply is set to true..."
.get();
}
In my before experience, I would like to break the whole flow by the protocols(HTTP, FTP, etc) as edges(inbound at the beginning and outbound as the end) in small flows. The inbound/outbound gateway is easy to set it to work without setting a reply channel etc, it should reply by default through the original route using its built-in protocols instead of channels. In my inboundGateway RSocket example, I don't set a reply channel there, but the message returns to the roscket routes and receive by the client side(outboudGateway).
Update: Finally it works, check here. An issue I encountered here when trying to use Amqp to send and receive object message, the are some class cast exception thrown, the TypeId in headers is NOT changed when using handle etc MessageHandler, have to transform bwteen json/object like the xml based cafe-amqp did to make it finally work. What is missing here?
There is already an official Java DSL sample in that repo: https://github.com/spring-projects/spring-integration-samples/tree/master/dsl/cafe-dsl.
Yes, there is no an AMQP variant, but if you follow an Amqp factory from spring-integration-amqp, that should not be too hard to convert that XML config to Java DSL.
Not sure what is that "bad option" for testing you use, but in the spring-integration-test we provide enough utils to simplify flows testing: https://docs.spring.io/spring-integration/docs/5.3.2.RELEASE/reference/html/testing.html#testing
UPDATE
Your problem is here:
<int-amqp:outbound-gateway
id="coldDrinksBarista"
request-channel="coldDrinks"
reply-channel="preparedJsonDrinks"
exchange-name="cafe-drinks"
routing-key="drink.cold"
amqp-template="amqpTemplate" />
where your config:
#Bean
public IntegrationFlow coldDrinksFlow(AmqpTemplate amqpTemplate) {
return IntegrationFlows
.from("coldDrinks")
.handle(
Amqp.outboundGateway(amqpTemplate)
.exchangeName(TOPIC_EXCHANGE_CAFE_DRINKS)
.routingKey(ROUTING_KEY_COLD_DRINKS)
)
.get();
}
doesn't have a similar replyChannel part. That why you get that reply channel or output channel is required because you send from the route which does not expect any replies. Probably you just haven't finished to design your application for the whole logic...

Spring Integration - Exceptions and retries

My setup:
I have a message daemon using ActiveMQ which will consume JSON messages.
The publisher of JSON messages adds type header with value, for ex, com.example.Foo which is the type of the json message. I use this to transform json to pojo.
Spring config:
Once the message is received, these are the steps it goes through:
1. Transformer: Transforms json to pojo
2. Payload type router: Based on the type of the pojo, routes the pojo to appropriate service activator.
3. Service activator: Process the message.
<int:chain input-channel="transformerChannel">
<int:transformer id="jsonToPojoTransformer" ref="JsonToPojoTransformer" method="transform" />
<int:payload-type-router default-output-channel="defaultChannel">
<int:mapping type="com.example.Foo" channel="fooHandlerChannel"/>
<int:mapping type="com.example.Bar" channel="barHandlerChannel"/>
</int:payload-type-router>
</int:chain>
<int:service-activator input-channel="fooHandlerChannel" ref="fooHandler" method="onMessage"/>
<int:service-activator input-channel="barHandlerChannel" ref="barHandler" method="onMessage"/>
Service activator definition:
public class FooHandler {
public void onMessage(Foo foo) {...}
}
Problem:
I want to know how to access the message headers in the service activator. It seems like the service activator does not have access to the message headers since the transformer is returning a pojo.
Lets say the service activator is unable to call a down stream rest service for whatever reason. I want to skip processing this message now and I want to retry this message later. Or lets say there was an exception in processing this message. I want to retry processing this message after some delay. How do I accomplish this?
--edit--
Removed details to reduce question size as per Artem's comment.
Please, try do not make so long topics here in SO. It's hard to answer particular question if there are to many of them.
Absolutely not clear why you can't get access to header from your service activator method. You can accept the whole Message<>, and call its getHeaders(). You can use#Headersannotation on theMaparg to get headers from the message. You can use#Header` annotation to extract exactly particular header from the message.
Even if your transformer method returns just a POJO that doesn't mean that it isn't wrapped to the Message with the headers from requestMessage. If you need to return specific header alongside with your POJO, you should create Message yourself, using MessageBuilder and don't forget to copy requestMessage headers, just because transformer doesn't copy request headers if the whole message is returned.
You have to support TX on your JMS consumer, so that RuntimeException will lead to the rollback and, therefore, redelivery eventually. And you should ensure that all the flow is performed in the same thread. Otherwise TX is committed and message is acked on the broker. The same happens when you don't have transactions.

JMS Inbound Gateway - error handling when reply destination queue send is not possible

We have an EIP flow which uses annotation-based spring:4.2.x APIs, spring-integration:4.2.x APIs and spring-integration-java-dsl:1.1.0 APIs to receive messages from a Websphere MQ Queue, do some processing and finally return responses to another Websphere MQ Queue. For this flow, we are using a JMS Inbound Gateway to synchronously receive messages from one queue, process them and send the responses back to another queue.
The JMS Inbound Gateway is configured with an errorChannel so that RuntimeExceptions are routed to it (this works fine). However during tests, when we purposely apply a PUT_INHIBIT on the flow's response Websphere MQ queue (i.e. to cause the flow to be unable to send responses back to the reply queue), the spring logs show the following WARNING log message:
WARN ... - Execution of JMS message listener failed, and no ErrorHandler has been set.
javax.jms.JMSException: MQJMS2007: failed to send message to MQ queue.
We know we can remove that WARNING log by configuring an ErrorHandler on the MLC itself but, the reason this is causing us problems is that when we route a response back, we actually route using a .routeToRecipients() call with .setIgnoreFailures(false) and two recipients - the first recipient routing to the JMS Inbound Gateway's replyChannel and the second routing to a post-send flow so that we can do DB updates, etc. The idea here being that if the first recipient send fails (i.e. when the response queue is not available), the post-send flow doesn't execute but instead an error handling flow executes instead (e.g. errorChannel flow). But in the described error scenario, we see the warning log, and the flow's post-send flow still executes instead of the errorChannel's flow...
It is as though, at this point, the JMS Inbound Gateway's errorChannel no longer applies. Is this correct? And is this intended behaviour? And if it is, does this mean that we should use Inbound/Outbound Adapters instead of an Inbound Gateway for our response post-send intent?
JMS MLC Configuration:
#Bean( destroyMethod = "shutdown")
public DefaultMessageListenerContainer serviceMLC() throws Exception {
DefaultMessageListenerContainer mlc = new DefaultMessageListenerContainer();
mlc.setAutoStartup(false);
mlc.setConnectionFactory(serviceCCF);
mlc.setDestination(requestMqQueue);
mlc.setAcceptMessagesWhileStopping(false);
return mlc;
}
JMS Inbound Gateway Configuration:
#Bean
public IntegrationFlow serviceFlow() {
return IntegrationFlows
.from(Jms
.inboundGateway(serviceMLC)
.autoStartup(true)
.defaultReplyDestination(responseMqQueue)
.replyChannel(responseOutCh)
.replyTimeout(180000)
.correlationKey("JMSCorrelationID")
.errorChannel(serviceErrorCh)
)
.channel(serviceInCh)
.get();
}
Yes; the gateway doesn't work that way.
When you send the reply to the gateway, it is queued in the gateway until the thread returns to the gateway; at which time, the reply is picked up and sent. So, the failure to send does not occur until later (after your second recipient flow is invoked).
Yes, to do what you want, you should use channel adapters instead because the failure will run directly on the calling thread.

spring rabbitmq and UI layer or managed bean

I have a rabbitmq listener as a separate class and JSF 2 managed bean.
In my bean I send a message and need to wait for result. I can't use sendAndReceive... because I send the message to one queue but receive from another queue, so I assign correlationId before sending.
So I need to wait asynchronously, I need to wait until right message comes to the listener. How to do it in rmq?
Looking at javadoc and source of RabbitTemplate it seems that he waits for response in reply queue. Do you set 'reply-to' property in your messages? If yes, then RabbitTemplate sendAndReceive methods should wait for response in 'reply-to' queue. Be sure to populate replyTo field correctly and test it.
Side note:
In RabbitMQ you do not send messages to the queue.
You send messages to the exchanges. Exchanges are routing messages to the queue(s) using bindings. With default or direct exchange type it looks like you send directly to the queue, but this is over-simplification.
See https://www.rabbitmq.com/tutorials/amqp-concepts.html for details.
Edit:
It seems there are some fix for that in AMQP 1.4.5.RELEASE
https://spring.io/blog/2015/05/08/spring-amqp-1-4-5-release-and-1-5-0-m1-available
Configurable Exchange/Routing Key for Replies
Previously, when using request/reply messaging with the
RabbitTemplate, replies were routed to the default exchange and routed
with the queue name. It is now possible to supply a reply-address with
the form exchange/routingKey to route using a specific exchange and
routing key.

Triggering Spring Integration

I'm still new to Spring Integration and Spring framework as a whole so please bear with me. I've been looking at this example.
https://github.com/benjaminwootton/spring-integration-examples/blob/master/target/classes/direct-channel-example.xml
https://github.com/benjaminwootton/spring-integration-examples/tree/master/src/main/java/examples/components
I'm wondering how do I or Spring trigger the method exactly?
I'm trying to do a round-robin using direct channel for my REST services. My REST services consumes messages and processes it.
I understand that with direct channel, Spring will round-robin through the subscribers but I'm not sure how Spring actually triggers that method.
Thank you for any help or advice.
The first class citizen in the Spring Integration is MessageChannel, so to allow for message (HTTP request) travel in the Integration flow, we should place message to some <channel>.
Since you say that you are in the REST service, I assume you use:
<int-http:inbound-gateway path="/path1,/path2"
request-channel="myChannel"/>
Here the myChannel is a component to where the HTTP request will be sent after conversion to the Spring Integration Message.
Of course, the MessageChannel is a pipe, when we push a thing into one side and there really should be something on the other side to poll that thing. In case of DirectChannel it is some subscriber. And we involve here the second class citizen - MessageHandler.
And if you use there something like <service-activator input-channel="myChannel" ref="foo" method="service">, the call stack may look like:
DirectChannel#send -> UnicastingDispatcher#dispatch ->
ServiceActivatingHandler#handleMessage -> MethodInvokingMessageProcessor#processMessage ->
MessagingMethodInvokerHelper#process -> foo#service
HTH

Categories

Resources