difference of Usage of MessagingException and ErrorMessage in spring integration - spring-integration

I have to analyze a spring integration piece of code given below :
<int:channel id="errorChannel" />
<int:exception-type-router input-channel="errorChannel"
default-output-channel="otherError">
<int:mapping
exception-type="MessageRejectedException"
channel="mreError" />
</int:exception-type-router>
<int:channel id="otherError" />
<int:transformer input-channel="otherError"
ref="otherExceptionTransformer" output-channel="errors" />
<bean id="otherExceptionTransformer"
class="OtherExceptionTransformer">
</bean>
<int:channel id="mreError" />
<int:transformer input-channel="mreError"
ref="mreExceptionTransformer" output-channel="errors" />
<bean id="mreExceptionTransformer"
class="MessageRejectedExceptionTransformer">
</bean>
<int:channel id="errors"/>
<int-jms:outbound-channel-adapter channel="errors"
connection-factory="connectionFactory" destination-
name="${myQueue.inbound.comp2}"/>
MessageRejectedExceptionTransformer takes as input as MessagingException object , however OtherExceptionTransformer takes ErrorMessage object as input.
Till now what I have understood is MessagingException contains the failedMessage and the cause of the exception,
whereas The ErrorMessage should contain the Message type.
Now , my main point of worry is , I am not understanding in which scenario , I won't be receiving MessagingException object .
I have tried to throw nullPointerException and IllegalArgumentException from my code , and spring processed both as Messaging Exception Only. So I am wondering when that otherError channel would receive a message.
Does anyone have a view on this?

The ErrorMessage has a payload of MessagingException which has two properties: failedMessage and cause (the original exception).
If a consumer (e.g. transformer) has an argument of MessagingException, the framework unwraps the payload from the message and invokes it. If the consumer takes a message (or ErrorMessage) it is invoked with the raw message.
Your router specifically routes on a MessageRejectedException, which is a subclass of MessagingException.
If any other MessagingException is thrown, the other route will be taken.

Related

Reply message received but the receiving thread has already received a reply

I have a flow that uses a pub/sub channel based gateway with 2 subscribers to send message to 2 SQS queues. In between I have chain to do message transformation. An aggregator is used to summarize the report of every gateway invocation. Happy path works fine, but when my transformer throws error I get this message Reply message received but the receiving thread has already received a reply and the aggregator gets invoked but future.get in the Runner never returns. Sample Config and code to test is as follows:
Config
<!-- Gateway to Publish Data to SQS -->
<task:executor id="dataExecutor" pool-size="10"/>
<int:publish-subscribe-channel id="dataChannel" task-executor="dataExecutor" apply-sequence="true"/>
<int:publish-subscribe-channel id="sqsResultChannel"/>
<int:publish-subscribe-channel id="gatewayErrorChannel"/>
<int:publish-subscribe-channel id="gatewayReplyChannel"/>
<int:gateway service-interface="com.abc.DataPublishGateway" id="dataPublishGateway"
error-channel="gatewayErrorChannel">
<int:method name="publishToDataService"
payload-expression="#args[0]"
request-channel="dataChannel"
reply-channel="gatewayReplyChannel">
</int:method>
</int:gateway>
<int:chain input-channel="gatewayErrorChannel" output-channel="sqsResultChannel">
<int:header-enricher>
<int:correlation-id expression="payload.failedMessage.headers.correlationId" />
<int:header name="sequenceSize" expression="payload.failedMessage.headers.sequenceSize" />
<int:header name="sequenceNumber" expression="payload.failedMessage.headers.sequenceNumber" />
</int:header-enricher>
</int:chain>
<!-- Route to system-a SQS -->
<int:channel id="sqsSystemAPublishChannel" />
<int:chain input-channel="dataChannel" output-channel="sqsSystemAPublishChannel">
<int:header-enricher>
<int:header name="targetSystem" value="system-a" />
</int:header-enricher>
<int:transformer expression="payload/2"/> <!-- Simulate transformer error -->
</int:chain>
<int-aws:sqs-outbound-channel-adapter sqs="amazonSQS"
queue="a-queue"
channel="sqsSystemAPublishChannel"
success-channel="sqsResultChannel"
failure-channel="gatewayErrorChannel"/>
<!-- Route to system-b SQS -->
<int:channel id="sqsSystemBPublishChannel" />
<int:chain input-channel="dataChannel" output-channel="sqsSystemBPublishChannel">
<int:header-enricher>
<int:header name="targetSystem" value="system-b" />
</int:header-enricher>
<int:transformer expression="payload.toLowerCase() "/>
</int:chain>
<int-aws:sqs-outbound-channel-adapter sqs="amazonSQS"
queue="b-queue"
channel="sqsSystemBPublishChannel"
success-channel="sqsResultChannel"
failure-channel="gatewayErrorChannel"/>
<bean class="com.abc.DataResponseAggregator" id="responseAggregator" />
<int:aggregator input-channel="sqsResultChannel" output-channel="gatewayReplyChannel" ref="responseAggregator"/>
<!-- Generic Error Channel Logger -->
<int:logging-channel-adapter log-full-message="true"
logger-name="errorLogger"
level="ERROR"
channel="errorChannel"
id="globalErrorLoggingAdapter"/>
Aggregator
public class DataResponseAggregator {
public Map<String, String> aggregate(List<Message> responses) {
Map<String, String> resultMap = new HashMap<>();
responses.forEach(message -> {
if (message instanceof ErrorMessage) {
String exceptionMessage = ((ErrorMessage) message).getPayload().getCause().getMessage();
String targetSystem = ((MessagingException) message.getPayload()).getFailedMessage().getHeaders()
.get("targetSystem").toString();
resultMap.put(targetSystem, exceptionMessage);
}
else {
String targetSystem = message.getHeaders().get("targetSystem").toString();
resultMap.put(targetSystem, "Ack -> " + message.getHeaders().get("aws_messageId").toString());
}
});
return resultMap;
}
}
Gateway
public interface DataPublishGateway {
Future<Map<String, String>> publishToDataService(String message);
}
Runner
#Bean
CommandLineRunner runner(DataPublishGateway dataPublishGateway) {
return args -> {
String[] messages = new String[]{"Message 1", "Message 2"};
List<Future<Map<String, String>>> futureList = new ArrayList<>();
Arrays.stream(messages).forEach(s -> {
futureList.add(dataPublishGateway.publishToDataService(s));
});
System.out.println("Processing Futures and Printing Results...");
futureList.forEach(mapFuture -> {
try {
mapFuture.get().entrySet().forEach(entry -> {
System.out.println(entry.getKey() + " - " + entry.getValue());
});
} catch (Exception e) {
e.printStackTrace();
}
});
};
}
Logs
14:47:21.650 INFO 91449 --- [pool-1-thread-1] o.s.i.h.s.MessagingMethodInvokerHelper : Overriding default instance of MessageHandlerMethodFactory with provided one.
2020-09-07 14:47:21.651 INFO 91449 --- [pool-1-thread-2] o.s.i.h.s.MessagingMethodInvokerHelper : Overriding default instance of MessageHandlerMethodFactory with provided one.
2020-09-07 14:47:21.655 WARN 91449 --- [pool-1-thread-2] cMessagingTemplate$TemporaryReplyChannel : Reply message received but the receiving thread has already received a reply: GenericMessage [payload={system-a=Expression evaluation failed: payload/2; nested exception is org.springframework.expression.spel.SpelEvaluationException: EL1030E: The operator 'DIVIDE' is not supported between objects of type 'java.lang.String' and 'java.lang.Integer', system-b=Ack -> 90b62dff-f3c3-4288-b5e3-8178e410f60d}, headers={aws_messageId=90b62dff-f3c3-4288-b5e3-8178e410f60d, replyChannel=org.springframework.messaging.core.GenericMessagingTemplate$TemporaryReplyChannel#1b519afe, sequenceNumber=2, errorChannel=org.springframework.messaging.core.GenericMessagingTemplate$TemporaryReplyChannel#1b519afe, sequenceSize=2, correlationId=dc914588-f9a9-537f-0623-94938f84cec4, aws_serviceResult={MD5OfMessageBody: 83b2330607fe8f817ce6d24249dea373,MD5OfMessageAttributes: 5f1f442c363809afbd334ff00232c834,MessageId: 90b62dff-f3c3-4288-b5e3-8178e410f60d,}, id=9a6ad209-5291-6216-100b-502b4c37eb01, targetSystem=system-b, timestamp=1599482841654}]
2020-09-07 14:47:21.655 WARN 91449 --- [pool-1-thread-1] cMessagingTemplate$TemporaryReplyChannel : Reply message received but the receiving thread has already received a reply: GenericMessage [payload={system-a=Expression evaluation failed: payload/2; nested exception is org.springframework.expression.spel.SpelEvaluationException: EL1030E: The operator 'DIVIDE' is not supported between objects of type 'java.lang.String' and 'java.lang.Integer', system-b=Ack -> 3c8a4479-22e1-48d8-aca6-a86c252e90c1}, headers={aws_messageId=3c8a4479-22e1-48d8-aca6-a86c252e90c1, replyChannel=org.springframework.messaging.core.GenericMessagingTemplate$TemporaryReplyChannel#f5a99c3, sequenceNumber=2, errorChannel=org.springframework.messaging.core.GenericMessagingTemplate$TemporaryReplyChannel#f5a99c3, sequenceSize=2, correlationId=74ba49e0-fef8-02e6-bc4b-b2ccdd7cd13b, aws_serviceResult={MD5OfMessageBody: 1db65a6a0a818fd39655b95e33ada11d,MD5OfMessageAttributes: cd4eae624b7c115f948034aafaba01bc,MessageId: 3c8a4479-22e1-48d8-aca6-a86c252e90c1,}, id=3e3de3f3-e8b0-470e-e62f-31815e662857, targetSystem=system-b, timestamp=1599482841654}]
What am I missing? I would expect the error from the transformer to follow the flow : gatewayErrorChannel -> sqsResultChannel -> aggregator -> gatewayReplyChannel which it does since the aggregator output appears in the WARN message. But why does future.get never return and also it seems like gatewayReplyChannel receives aggregator output twice?
With some logging and most importantly the understanding of error and reply channels set on Gateway messages from this post, I am able to solve the issue.
What was the issue here?
The errorChannel was not set on the messages coming into the chain.
In case when both chains throw exceptions, the error messages coming to the aggregator did not have a reply channel set on them to return the aggregated message to the gateway's reply channel. The final config looks as below:
<!-- Gateway to Publish Data to SQS -->
<task:executor id="dataExecutor" pool-size="10"/>
<int:publish-subscribe-channel id="dataChannel" task-executor="dataExecutor" apply-sequence="true"/>
<int:publish-subscribe-channel id="sqsResultChannel"/>
<int:publish-subscribe-channel id="gatewayErrorChannel"/>
<int:publish-subscribe-channel id="gatewayReplyChannel"/>
<int:gateway service-interface="com.abc.DataPublishGateway" id="dataPublishGateway"
error-channel="gatewayErrorChannel">
<int:method name="publishToDataService"
payload-expression="#args[0]"
request-channel="dataChannel"
reply-channel="gatewayReplyChannel">
</int:method>
</int:gateway>
<int:chain input-channel="gatewayErrorChannel" output-channel="sqsResultChannel">
<int:header-enricher>
<int:correlation-id expression="payload.failedMessage.headers.correlationId" />
<int:header name="sequenceSize" expression="payload.failedMessage.headers.sequenceSize" />
<int:header name="sequenceNumber" expression="payload.failedMessage.headers.sequenceNumber" />
<int:reply-channel expression="payload.failedMessage.headers.replyChannel" />
</int:header-enricher>
</int:chain>
<!-- Route to system-a SQS -->
<int:channel id="sqsSystemAPublishChannel" />
<int:chain input-channel="dataChannel" output-channel="sqsSystemAPublishChannel">
<int:header-enricher>
<int:header name="targetSystem" value="system-a" />
<int:error-channel ref="gatewayErrorChannel" overwrite="true" />
</int:header-enricher>
<int:transformer expression="payload/2"/> <!-- Simulate transformer error -->
</int:chain>
<int-aws:sqs-outbound-channel-adapter sqs="amazonSQS"
queue="a-queue"
channel="sqsSystemAPublishChannel"
success-channel="sqsResultChannel"
failure-channel="gatewayErrorChannel"/>
<!-- Route to system-b SQS -->
<int:channel id="sqsSystemBPublishChannel" />
<int:chain input-channel="dataChannel" output-channel="sqsSystemBPublishChannel">
<int:header-enricher>
<int:header name="targetSystem" value="system-b" />
<int:error-channel ref="gatewayErrorChannel" overwrite="true" />
</int:header-enricher>
<int:transformer expression="payload.toLowerCase() "/>
</int:chain>
<int-aws:sqs-outbound-channel-adapter sqs="amazonSQS"
queue="b-queue"
channel="sqsSystemBPublishChannel"
success-channel="sqsResultChannel"
failure-channel="gatewayErrorChannel"/>
<bean class="com.abc.DataResponseAggregator" id="responseAggregator" />
<int:aggregator input-channel="sqsResultChannel" output-channel="gatewayReplyChannel" ref="responseAggregator"/>
<!-- Generic Error Channel Logger -->
<int:logging-channel-adapter log-full-message="true"
logger-name="errorLogger"
level="ERROR"
channel="errorChannel"
id="globalErrorLoggingAdapter"/>

WARN JdbcChannelMessageStore Message with id was not deleted

I have a queue channel backed by a JdbcChannelMessageStore. I have two instances of this application and with high concurrency I have this warning:
2020-03-13 19:25:38,209 task-scheduler-5 WARN JdbcChannelMessageStore:652 - Message with id '06b73eab-727a-780f-d0fa-1b0e0dd1ea20' was not deleted.
Is there a way to remove them?
As far as my understanding, messages are being read twice, am I correct?
I am using SI 4.3.19.RELEASE. Here is my spring flow
<int:channel id="channel">
<int:queue message-store="messageStoreBean"/>
</int:channel>
<int:header-value-router input-channel="channel
header-name="name" >
<int:poller max-messages-per-poll="2" fixed-rate="500" >
<int:transactional />
</int:poller>
...
</int:header-value-router>
<bean id="storeQueryProviderBean" class="org.springframework.integration.jdbc.store.channel.PostgresChannelMessageStoreQueryProvider" />
<bean id="messageStoreBean" class="org.springframework.integration.jdbc.store.JdbcChannelMessageStore">
<property name="dataSource" ref="messageStoreDataSource" />
<property name="channelMessageStoreQueryProvider" ref="storeQueryProviderBean" />
<property name="region" value="region" />
</bean>
It looks like PostgreSQL doesn't guarantee exclusive reading with transactions and LIMIT 1 FOR UPDATE.
Anyway that WARN is just a note that some other process has removed the message. Nothing is duplicated if other process is similar to that poller:
public Message<?> pollMessageFromGroup(Object groupId) {
final String key = getKey(groupId);
final Message<?> polledMessage = this.doPollForMessage(key);
if (polledMessage != null) {
if (!this.doRemoveMessageFromGroup(groupId, polledMessage)) {
return null;
}
}
return polledMessage;
}
You see if message was not removed, we return null therefore nothing to poll at the moment.
You can turn off the warning level for the org.springframework.integration.jdbc.store.JdbcChannelMessageStore to avoid that message specifying a category level as ERROR in your logging config.

Spring Integration gateway with multiple parameters to construct url

In current model, we have a REST endpoint, which gets requestbody, based on which a jms text message is created and sent to JMS queue,
TextMessage outMessage = session.createTextMessage(messagePayloadText);
..
outMessage.setStringProperty("clientType", clientType);
outMessage.setStringProperty("DYNAMIC", dynaHeader);
In above code DYNAMIC is required to help me in creating our url
<int:chain input-channel="gCStatusInChannel" output-channel="headerFilterChannel">
<int:header-enricher>
<int:header name="Api-Key" value="B8872853E8B"></int:header>
<int:header name="Accept" value="application/json" />
<int:header name="Content-Type" value="application/json" />
</int:header-enricher>
<int-http:outbound-gateway
url="https://i-zaie.sr13.tst.bst/ia-zaaie/rest/search/v2/cReference/{cref}"
http-method="PUT"
header-mapper="headerMapper"
expected-response-type="java.lang.String"
encode-uri="false"
request-factory="sslFactory">
<int-http:uri-variable name="cref" expression="headers['DYNAMIC']" />
</int-http:outbound-gateway>
<int:object-to-string-transformer></int:object-to-string-transformer>
</int:chain>
Everything works in this model. Now I want to use gateway instead of JMS
New code:
<int:gateway id="gService"
service-interface="n.d.lr.eai.gw.GGateway"
default-reply-channel="dest-channel"
default-request-timeout="5000" default-reply-timeout="5000">
<int:method name="vCreateSignal" request-channel="vCreateSignalInChannel"/> ...
Question:
can i have method in gateway as below?
public String vCreateSignal(String caseDat, String dynamic);
what should I do to enable
<int:chain input-channel="gCStatusInChannel"...
..>
to get headers['DYNAMIC'] value and continue.
Yes, you can do that. What you just need is to add a #Header("DYNAMIC") into that dynamic parameter:
public String vCreateSignal(String caseDat, #Header("DYNAMIC") String dynamic);
And when you call this gateway's method you just specify an argument and it will be mapped to an appropriate header and that all: https://docs.spring.io/spring-integration/docs/current/reference/html/messaging-endpoints-chapter.html#gateway-mapping

Reuse of service-activator for several gateway methods with Splitter

does somebody happen to know if it is valid to reuse a service activator and so also the output-channel using several methods (inbound) especially with a splitter and aggregator.
--> Always with result on the gateway.
In several tests it seems to work fine. As soon I added a splitter with an aggregator I get wrongs result routed to the gateway which then fails with a conversion exception (here in my case it cannot convert boolean to integer).
Thanks,
Paul
Flow
Spring Integration Config
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">
<int:gateway service-interface="activity.BulbMessageGateway">
<int:method name="sendToBulb" request-channel="bulbMessages" reply-channel="bulbSendResult"></int:method>
<int:method name="updateHomeLightStatus" request-channel="homeBulbEntity" reply-channel="homeBulbEntityResult">
</int:method>
<int:method name="updateLightStatus" request-channel="bulbEntities" reply-channel="bulbSendResult">
<int:header name="homeId" expression="#args[0].homeId"/>
<int:header name="bulbId" expression="#args[0].strongId"/>
</int:method>
</int:gateway>
<int:channel id="bulbMessages" />
<int:channel id="bulbSendResult" />
<int:channel id="bulbEntities" />
<int:channel id="homeBulbEntity" />
<int:channel id="homeBulbEntityResult" />
<int:chain input-channel="homeBulbEntity" output-channel="bulbEntities">
<int:splitter expression="payload.bulbs" />
<int:header-enricher>
<int:header name="bulbId" expression="payload.strongId"/>
<int:header name="homeId" expression="payload.homeId"/>
</int:header-enricher>
</int:chain>
<int:transformer method="bulbToLightStatus" input-channel="bulbEntities" output-channel="bulbMessages">
<bean class="util.BulbTransformer"></bean>
</int:transformer>
<int:aggregator input-channel="bulbSendResult" output-channel="homeBulbEntityResult" method="aggregate">
<bean class="util.BooleanAggregator" />
</int:aggregator>
<int:service-activator input-channel="bulbMessages" output-channel="bulbSendResult" method="send">
<bean class="activity.BulbWebsocketMessageSenderBA" />
</int:service-activator>
</beans>
Unit test
#Test
public void sendMessageNoReceiver() {
assertFalse(gateway.sendToBulb(new HomeId("1"), new BulbId("1"), BulbMessageBuilder.restart("foo")));
}
#Test
public void sendMessageWithReceiver() {
MockSession<BulbId, BulbBE> bulbSession = new MockSession<BulbId, BulbBE>(new BulbBE(HomeId.of("1"), BulbId.of("1"), "bulb", "pass"));
registry.addBulbSession(bulbSession);
assertTrue(gateway.sendToBulb(new HomeId("1"), new BulbId("1"), BulbMessageBuilder.restart("foo")));
assertEquals(1, bulbSession.receivedMessages());
}
#Test
public void updateBulbStatus() {
final MockSession<BulbId, BulbBE> bulbSession1 = new MockSession<BulbId, BulbBE>(new BulbBE(HomeId.of("1"), BulbId.of("1"), "bulb", "pass"));
assertFalse(gateway.updateLightStatus(bulbSession1.getIdentity()));
registry.addBulbSession(bulbSession1);
assertTrue(gateway.updateLightStatus(bulbSession1.getIdentity()));
assertEquals(1, bulbSession1.receivedMessages());
final MockSession<BulbId, BulbBE> bulbSession2 = new MockSession<BulbId, BulbBE>(new BulbBE(HomeId.of("1"), BulbId.of("2"), "bulb", "pass"));
assertFalse(gateway.updateLightStatus(bulbSession2.getIdentity()));
registry.addBulbSession(bulbSession2);
assertTrue(gateway.updateLightStatus(bulbSession2.getIdentity()));
assertTrue(gateway.updateLightStatus(bulbSession2.getIdentity()));
assertEquals(2, bulbSession2.receivedMessages());
assertEquals(1, bulbSession1.receivedMessages());
}
#Test
public void updateHomeBulbStatus() {
final HomeBE home = new HomeBE();
home.setId(new ObjectId());
final MockSession<BulbId, BulbBE> bulbSession1 = new MockSession<BulbId, BulbBE>(new BulbBE(home.getStrongId(), BulbId.of("1"), "bulb", "pass"));
registry.addBulbSession(bulbSession1);
final MockSession<BulbId, BulbBE> bulbSession2 = new MockSession<BulbId, BulbBE>(new BulbBE(home.getStrongId(), BulbId.of("2"), "bulb", "pass"));
registry.addBulbSession(bulbSession2);
home.addBulb(bulbSession1.getIdentity());
assertEquals(1, gateway.updateHomeLightStatus(home));
assertEquals(1, bulbSession1.receivedMessages());
assertEquals(0, bulbSession2.receivedMessages());
home.addBulb(bulbSession2.getIdentity());
assertEquals(2, gateway.updateHomeLightStatus(home));
assertEquals(2, bulbSession1.receivedMessages());
assertEquals(1, bulbSession2.receivedMessages());
}
The last test fails if it is executed together with the other tests. It passes if it is executed alone.
The error is that the last method (using the splitter) receives now a boolean, which seems to be a result of the other two methods registered. The result of these methods is a boolean.
Please, share config on the matter.
And according your graph it would be better if you'd minimize the config as much as possible to isolate the problem.
From other side, please, be more specific: your question is fully unclear.
That is your own service. How can we be sure that it is safe to be used in different places? Only you, as an author, can determine that.
UPDATE
Sorry for the delay. Was busy with the release.
And thank for sharing the config for your use-case.
Now I see the problem.
You use everywhere on the gateway's methods a reply-channel. See the documentation on the matter when you need that:
Typically you don’t have to specify the default-reply-channel, since a Gateway will auto-create a temporary, anonymous reply channel, where it will listen for the reply. However, there are some cases which may prompt you to define a default-reply-channel (or reply-channel with adapter gateways such as HTTP, JMS, etc.).
Since you use the same bulbSendResult in different places the behavior is really unpredictable. Moreoever that channel is DirectChannel, so the round-robin balancer are on the scene.
You should get rid of those reply-channel's at all and just rely from your downstream components on the replyChannel header. Therefore you should remove those output-channels in the components which are intended to return replies to your gateway.
For example the last service-activator should be just like this:
<int:service-activator input-channel="bulbMessages" method="send">
<bean class="activity.BulbWebsocketMessageSenderBA"/>
</int:service-activator>
Since you general question how to reuse this service-activator, I'm answering to the question having the config from you:
<int:chain input-channel="homeBulbEntity">
<int:splitter expression="payload.bulbs"/>
<int:header-enricher>
<int:header name="bulbId" expression="payload.strongId"/>
<int:header name="homeId" expression="payload.homeId"/>
</int:header-enricher>
<int:transformer method="bulbToLightStatus">
<bean class="util.BulbTransformer"/>
</int:transformer>
<int:gateway request-channel="bulbMessages"/>
<int:aggregator method="aggregate">
<bean class="util.BooleanAggregator"/>
</int:aggregator>
</int:chain>
Pay attention to the absent output-channel for the <chain>. Therefore it sends reply directly to the replyChannel from headers and as a return to your updateHomeLightStatus gateway's method.
Another trick is that <int:gateway request-channel="bulbMessages"/> which sends messages in the middle of the <chain> flow to your <service-activator> and wait for the reply from there exactly the same way as a top-level gateway - via replyChannel header. For the <service-activator> without an output-channel it is a black-box where to send the reply. It uses just replyChannel from headers!
After receiving the reply gateway in the <chain> push the message to the <aggregator>.
When aggregator will do its logic, the result will be send to the top-level gateway as an output from the <chain>.
That's all.
Let me know what else isn't clear here.

How to extract the data from a kafka inbound message with spring

I am building a Kafka consumer with spring. My configuration seems to be pretty straightforward. Messages are being consumed and saved in files. However, the payload is cryptic and I can't get the data (short "hello world" messages).
This is what I get when I access the payload (e.g. when I set up a transformer bean btween in inbound kafka and the outboud file:
{test-topic={0=[[B#713c9d72, [B#7d656f90, [B#26bb8c83, [B#4b959d83 [B#5ed74e8e]}}
My question is: How do I access the actual payload (the "hellow world" string")?
My configuration is :
<int:channel id="inputFromKafka">
<int:queue />
</int:channel>
<int:poller
max-messages-per-poll="5" default = "true" fixed-delay="10" time-unit="MILLISECONDS"/>
<int-kafka:inbound-channel-adapter
id="kafkaInboundChannelAdapter" kafka-consumer-context-ref="consumerContext"
auto-startup="true" channel="inputFromKafka">
<int-kafka:consumer-context id="consumerContext"
consumer-timeout="40000" zookeeper-connect="zookeeperConnect">
<int-kafka:consumer-configurations>
<int-kafka:consumer-configuration
group-id="group12" max-messages="5">
<int-kafka:topic id="test-topic" streams="1" />
</int-kafka:consumer-configuration>
</int-kafka:consumer-configurations>
</int-kafka:consumer-context>
<int-kafka:zookeeper-connect id="zookeeperConnect"
zk-connect="localhost:2181" zk-connection-timeout="6000"
zk-session-timeout="6000" zk-sync-time="2000" />
<file:outbound-channel-adapter id="filesOut"
directory="/tmp/fromKafka">
</file:outbound-channel-adapter>
You are seeing raw byte[].
Add...
<bean id="decoder"
class="org.springframework.integration.kafka.serializer.common.StringDecoder" />
and
<int-kafka:consumer-configuration
value-decoder="decoder"
...

Resources