Using Spring-Integration-Kafka can we still use #MessagingGateway and #Gateway.
My current code looks like this:
#MessagingGateway
public interface OrderGateway {
#Gateway(requestChannel = "requestChannel", replyChannel = "replyChannel",headers = {#GatewayHeader(name = "kafka_topic", value ="requestTopic"))
Order order(Item item)
}
on my Spring Spring configuration:
#Bean
#ServiceActivator(inputChannel = "requestChannel")
public MessageHandler kafkaMessageHandler(KafkaTemplate kafkaTemplate) {
KafkaProducerMessageHandler<String, String> messageHandler = new KafkaProducerMessageHandler<>(kafkaTemplate);
messageHandler.setMessageKeyExpression(new LiteralExpression("spring-integration-kafka"));
messageHandler.setTopicExpression(new SpelExpressionParser().parseExpression("headers.kafka_topic"));
return messageHandler;
}
with this setup I get and error saying:
by: org.springframework.messaging.core.DestinationResolutionException: no output-channel or replyChannel header available
You don't appear to have shown the complete configuration.
The gateway is expecting a reply but the kafkaMessageHandler produces no reply (unless the template is a ReplyingKafkaTemplate) and will lose the replyChannel header.
So, presumably, you are trying to send a reply from someplace else.
If you are expecting request/reply semantics; use the new outbound gateway.
Where the template has to be a ReplyingKafkaTemplate.
Related
I'm trying to send the UDP request and receive the response. Spring Integration has the appropriate instruments for such kind of task: UnicastSendingMessageHandler and UnicastReceivingChannelAdapter. I configured it in the following way
#Bean
public MessageChannel requestChannel() {
return new DirectChannel();
}
#Bean
#ServiceActivator(inputChannel = "requestChannel")
public UnicastSendingMessageHandler unicastSendingMessageHandler() {
UnicastSendingMessageHandler unicastSendingMessageHandler = new UnicastSendingMessageHandler("239.255.255.250", 1982);
return unicastSendingMessageHandler;
}
#Bean
public UnicastReceivingChannelAdapter unicastReceivingChannelAdapter() {
UnicastReceivingChannelAdapter unicastReceivingChannelAdapter = new UnicastReceivingChannelAdapter(8080);
unicastReceivingChannelAdapter.setOutputChannelName("nullChannel");
return unicastReceivingChannelAdapter;
}
How I send a message (I'm using sendDiscoveryMessage() wherever I want):
#Service
public class DiscoveryService {
private static final String DISCOVERY_MESSAGE = "M-SEARCH * HTTP/1.1\r\n"
+ "HOST: 239.255.255.250:1982\r\n"
+ "MAN: \"ssdp:discover\"\r\n"
+ "ST: wifi_bulb";
private final MessageChannel requestChannel;
public DiscoveryService(final MessageChannel requestChannel) {
this.requestChannel = requestChannel;
}
public void sendDiscoveryMessage() {
requestChannel.send(new GenericMessage<>(DISCOVERY_MESSAGE));
}
}
At this point, I can check the packets via WireShark and ensure that Datagram was sent and the appropriate response was sent too.
The only question is how to receive this response. As far as I understand reading the documentation, I need the method annotated with #ServiceActivator. But I don't understand where (which channel) I should receive the response (in order to correctly specify #ServiceActivator(inputChannel="")). Also, I'm not sure about #ServiceActivator(inputChannel = "requestChannel") I put for UnicastSendingMessageHandler bean.
I tried to create the following method(assuming that the response will come to the same channel):
#ServiceActivator(inputChannel = "requestChannel")
public void receiveResponse(Message<String> response) {
System.out.println(response);
}
but it actually intercepts my own request message (seems logical to me, because I send the request to requestChannel).
So I don't understand how many channels I need (maybe I need 1 for request and 1 for response) and how to create #ServiceActivator to catch the response.
unicastReceivingChannelAdapter.setOutputChannelName("nullChannel");
You are sending the result to nullChannel which is like /dev/null on Unix; you are discarding it.
Use #ServiceActivator(inputChannel = "replyChannel") and
unicastReceivingChannelAdapter.setOutputChannelName("replyChannel");
I'm working on an integration with a REST service, the idea is that it's polled by an outbound gateway marketingCategoryOutboundGateway implemented by HttpRequestExecutingMessageHandler. The gateway makes a request to the REST service and pushes its response to the marketingCategory channel. The gateway itself is triggered by a message created by marketingCategoryPollerMessageSource using the makeTriggeringMessage factory method.
The problem is that the service returns paginated results. I something which would listen on the marketingCategory channel, apart from the service activator I already have, check if the response and push a new message with an incremented page number created by makeTriggeringMessage to the marketingCategoryPoller channel, so that the code would spin in a loop until it fetches all the pages from the REST service.
Does Spring Integration allow to make such filters which receive one message on the input channel, test it against a condition and push a new message to the output channel if the condition is true?
The code:
//Responses from the REST service go to this channel
#Bean("marketingCategory")
MessageChannel marketingCategory() { return new PublishSubscribeChannel();}
//This channel is used to trigger the outbound gateway which makes a request to the REST service
#Bean
MessageChannel marketingCategoryPoller() {return new DirectChannel();}
//An adapter creating triggering messages for the gateway
#Bean
#InboundChannelAdapter(channel = "marketingCategoryPoller", poller = #Poller(fixedDelay = "15000"))
public MessageSource<String> marketingCategoryPollerMessageSource() { return () -> makeTriggeringMessage(1);}
//A factory for producing messages which trigger the gateway
private Message<String> makeTriggeringMessage(int page) {
//make a message for triggering marketingCategoryOutboundGateway
return MessageBuilder.withPayload("")
.setHeader("Host", "eclinic")
.setHeader("page", page)
.build();
}
//An outbound gateway, makes a request to the REST service and returns the response to marketingCategory channel
#Bean
#ServiceActivator(inputChannel = "marketingCategoryPoller")
public MessageHandler marketingCategoryOutboundGateway(#Qualifier("marketingCategory") MessageChannel channel) {
//make a request to the REST service and push the response to the marketingCategory channel
}
//handler for REST service responses
#Bean
#ServiceActivator(inputChannel = "marketingCategory")
public MessageHandler marketingCategoryHandler() {
return (msg) -> {
//process the categories returned by marketingCategoryOutboundGateway
};
}
I've found a solution based on this posting Read and download from a paginated REST-Services with spring integration:
Trigger the outbound gateway which talks to the REST service and pushes the response to a channel using an inbound channel adapter with a poller.
The inbound channel adapter is a message source which originally generates a message with a header indicating the page number to fetch from the REST API.
The page message header is used by the outbound gateway to generate a url specifying the desired page
The channel to which the outbound gateway pushes REST service responses has 2 subscribers:
2.1. a service activator which does something with the fetched data
2.2. a filter, which checks if this is the last page and if not, it sends the message further to another channel used by a header enricher
Having received a message, the header enricher increments its page header and pushes the message further to the channel which triggers the outbound gateway,
the gateway read the incremented page header and fetches the next page from the REST service
The loop keeps spinning until the REST service returns the last page. The filter doesn't let this message to pass through to the header enricher breaking the loop.
Full code:
#Configuration
public class IntegrationConfiguration {
private final ApiGateConfig apiGateConfig;
IntegrationConfiguration(ApiGateConfig apiGateConfig) {
this.apiGateConfig = apiGateConfig;
}
#Bean("marketingCategory")
MessageChannel marketingCategory() {
return new PublishSubscribeChannel();
}
#Bean
MessageChannel marketingCategoryPoller() {
return new DirectChannel();
}
#Bean
MessageChannel marketingCategoryPollerNextPage() {
return new DirectChannel();
}
#Bean
#InboundChannelAdapter(channel = "marketingCategoryPoller", poller = #Poller(fixedDelay = "15000"))
public MessageSource<RestPageImpl<MarketingCategory>> marketingCategoryPollerMessageSource() {
return () -> makeTriggeringMessage(0);
}
/**
* Build a gateway triggering message
*/
private Message<RestPageImpl<MarketingCategory>> makeTriggeringMessage(int page) {
return MessageBuilder.withPayload(new RestPageImpl<MarketingCategory>())
.setHeader("Host", "eclinic")
.setHeader("page", page)
.build();
}
#Bean
#ServiceActivator(inputChannel = "marketingCategoryPoller")
public MessageHandler marketingCategoryOutboundGateway(#Qualifier("marketingCategory") MessageChannel channel) {
String uri = apiGateConfig.getUri() + "/marketingCategories?page={page}";
//the type of the payload
ParameterizedTypeReference<RestPageImpl<MarketingCategory>> type = new ParameterizedTypeReference<>() {
};
//page number comes from the message
SpelExpressionParser expressionParser = new SpelExpressionParser();
var uriVariables = new HashMap<String, Expression>();
uriVariables.put("page", expressionParser.parseExpression("headers.page"));
HttpRequestExecutingMessageHandler handler = new HttpRequestExecutingMessageHandler(uri);
handler.setHttpMethod(HttpMethod.GET);
handler.setExpectedResponseTypeExpression(new ValueExpression<>(type));
handler.setOutputChannel(channel);
handler.setUriVariableExpressions(uriVariables);
return handler;
}
#Bean
#ServiceActivator(inputChannel = "marketingCategory")
public MessageHandler marketingCategoryHandler() {
return (msg) -> {
var page = (RestPageImpl<MarketingCategory>) msg.getPayload();
System.out.println("Page #" + page.getNumber());
page.getContent().forEach(c -> System.out.println(c.getMarketingCategory()));
};
}
#Filter(inputChannel = "marketingCategory", outputChannel = "marketingCategoryPollerNextPage")
public boolean marketingCategoryPaginationFilter(RestPageImpl<MarketingCategory> page) {
return !page.isLast();
}
#Bean
#Transformer(inputChannel = "marketingCategoryPollerNextPage", outputChannel = "marketingCategoryPoller")
HeaderEnricher incrementPage() {
Map<String, HeaderValueMessageProcessor<?>> headersToAdd = new HashMap<>();
Expression expression = new SpelExpressionParser().parseExpression("headers.page+1");
var valueProcessor = new ExpressionEvaluatingHeaderValueMessageProcessor<>(expression, Integer.class);
valueProcessor.setOverwrite(true);
headersToAdd.put("page", valueProcessor);
return new HeaderEnricher(headersToAdd);
}
}
We're using Spring integration in my application. I'd like put some objects into channel for asynchronous processing and error handling. So for this, I configured MessageGateway with error channel and PollableChannel for handling objects to be processed.
The problem
So I'm calling messageGateway.processMessage(message) to put message into channel. This works as expected - calling this method is non-blocking, messages get processed and are forwarded to next channel. But when processing method throws an exception, it is not redirected to error channel.
On the other hand when I change my processing channel from PollableChannel to SubscribableChannel, error channel works as expected, but calling the gateway is of course blocking. What am I missing? Can I have both non blocking call and error channel?
The code
Component doing the message processing:
#Component
public MessageProcessor {
#Transactional
#ServiceActivator(inputChannel = "msg.process", outputChannel = "msg.postprocess")
public void processMessage(MyMessage message) {
// Message processing that may throw exception
}
}
Channel definition:
#Configuration
public class IntegrationConfig {
#Bean(name = "msg.process")
private MessageChannel processChannel() {
return new RendezvousChannel();
}
#Bean(name = "msg.error")
private MessageChannel errorChannel() {
return new DirectChannel();
}
}
My gateway looks like this:
#MessagingGateway(errorChannel = "msg.error")
public interface MessagingGateway {
#Gateway(requestChannel = "msg.processing")
void processMessage(MyMessage message);
}
Error handler:
#Component
public ErrorHandlers {
#Transactional
#ServiceActivator(inputChannel = "msg.error")
public void processError(MessagingException me) {
// Error handling is here
}
}
But when processing method throws an exception, it is not redirected to error channel.
When a gateway method returns void, the calling thread is released immediately when it returns to the gateway.
The gateway does not add an error channel header in this case (in the next release - 5.0) we have changed that.
In the meantime, you can use a header enricher to set the errorChannel header to your error channel. You can also use the defaultHeaders property on the #MessagingGateway - see the comments on this answer.
I wrote a simple message flow with request and reply. I have to use two independent queues so i declare AmqpOutboundAdapter to send a message and AmqpInboundAdapter to receive a reply.
#Bean
#FindADUsers
public AmqpOutboundEndpoint newFindADUsersOutboundAdapter() {
return Amqp.outboundAdapter(amqpTemplate())
.routingKeyExpression("headers[" + ADUsersFindConfig.ROUTING_KEY_HEADER + "]")
.exchangeName(getExchange())
.headerMapper(amqpHeaderMapper())
.get();
}
#Bean
public AmqpInboundChannelAdapter newFindADUsersResponseInboundChannelAdapter(
ADUsersFindResponseConfig config) {
return Amqp.inboundAdapter(rabbitConnectionFactory(), findADUsersResponseQueue)
.headerMapper(amqpHeaderMapper())
.outputChannel(config.newADUsersFindResponseOutputChannel())
.get();
}
It should work with #MessagingGateway:
#MessagingGateway
public interface ADUsersFindService {
String FIND_AD_USERS_CHANNEL = "adUsersFindChannel";
String FIND_AD_USERS_REPLY_OUTPUT_CHANNEL = "adUsersFindReplyOutputChannel";
String FIND_AD_USERS_REPLY_CHANNEL = "adUsersFindReplyChannel";
String CORRELATION_ID_REQUEST_HEADER = "correlation_id";
String ROUTING_KEY_HEADER = "replyRoutingKey";
String OBJECT_TYPE_HEADER = "object.type";
#Gateway(requestChannel = FIND_AD_USERS_CHANNEL, replyChannel = FIND_AD_USERS_REPLY_CHANNEL)
ADResponse find(ADRequest adRequest, #Header(ROUTING_KEY_HEADER) String routingKey, #Header(OBJECT_TYPE_HEADER) String objectType);
}
And the ADUsersFindResponseConfig class looks like:
#Configuration
#Import(JsonConfig.class)
public class ADUsersFindResponseConfig {
#Autowired
public NullChannel nullChannel;
#Autowired
private JsonObjectMapper<?, ?> mapper;
/**
* #return The output channel for the flow
*/
#Bean(name = ADUsersFindService.FIND_AD_USERS_REPLY_OUTPUT_CHANNEL)
public MessageChannel newADUsersFindResponseOutputChannel() {
return MessageChannels.direct().get();
}
/**
* #return The output channel for gateway
*/
#Bean(name = ADUsersFindService.FIND_AD_USERS_REPLY_CHANNEL)
public MessageChannel newADUsersFindResponseChannel() {
return MessageChannels.direct().get();
}
#Bean
public IntegrationFlow findADUsersResponseFlow() {
return IntegrationFlows
.from(newADUsersFindResponseOutputChannel())
.transform(new JsonToObjectTransformer(ADResponse.class, mapper))
.channel(newADUsersFindResponseChannel())
.get();
}
}
Sending message works properly, but i have a problem with receiving message. I am expecting that received message will be passed to channel called FIND_AD_USERS_REPLY_OUTPUT_CHANNEL, then the message will be deserialized to ADResponse object using findADUsersResponseFlow , and next ADResponse object will be passed to gateway replyChannel - FIND_AD_USERS_REPLY_CHANNEL. Finally, 'find' method return this object. Unfortunately when org.springframework.integration.handler.BridgeHandler receive a message, i got exception:
org.springframework.messaging.MessagingException: ; nested exception is org.springframework.messaging.core.DestinationResolutionException: no output-channel or replyChannel header available
Message log looks like:
11:51:35.697 [SimpleAsyncTaskExecutor-1] INFO New message - GenericMessage [payload={...somepayload...}, headers={correlation_id=7cbd958e-4b09-4e4c-ba8e-5ba574f3309a, replyRoutingKey=findADUsersResponse.ad, amqp_consumerQueue=findADUsersResponseQueue, history=newFindADUsersResponseInboundChannelAdapter,adUsersFindReplyOutputChannel,adUsersFindReplyChannel,infoLog,infoLoggerChain.channel#0,infoLoggerChain.channel#1, id=37a4735d-6983-d1ad-e0a1-b37dc17e48ef, amqp_consumerTag=amq.ctag-8Qs5YEun1jXYRf85Hu1URA, object.type=USER, timestamp=1469094695697}]
So i'm pretty sure that message was passed to adUsersFindReplyChannel. Also (if it's important) both request message and reply message have 'replyTo' header set to null. What am I doing wrong?
The replyChannel header is a live object and can't be serialized over AMQP.
You can use an outbound gateway instead of the pair of adapters and the framework will take care of the headers.
If you must use adapters for some reason, you need to do 2 things:
Use the header channel registry to convert the channel object to a String which is registered with the registry.
Make sure that the header mapper is configured to send/receive the replyChannel header and that your receiving system returns the header in the reply.
I am trying to convert file:outbound-gateway configuration in XML to Java config, but can't find the correct API.
XML:
<file:outbound-gateway directory="file:myDir"
request-channel="inFiles" auto-create-directory="true"
delete-source-files="true" reply-channel="outFiles">
</file:outbound-gateway>
This is what I have so far on Java configuration. Not sure how to set request-channel and reply-channel:
#Bean
public MessageHandler fileOutBoundGateway() {
FileWritingMessageHandler gateway = new FileWritingMessageHandler(new File("myDir"));
gateway.setDeleteSourceFiles(true);
gateway.setAutoCreateDirectory(true);
// FIXME need to set request and reply channel
return gateway;
}
The request-channel (inputChannel) is an option of the endpoint. In your case you have only channel and MessageHandler, but there is still no endpoint.
Only what you need is service-activator:
#Bean
#ServiceActivator(inputChannel = "input")
public MessageHandler fileOutBoundGateway() {
FileWritingMessageHandler gateway = new FileWritingMessageHandler(new File("myDir"));
gateway.setDeleteSourceFiles(true);
gateway.setAutoCreateDirectory(true);
gateway.setOutputChannel(outputChannel());
return gateway;
}
Please, find more info in the Reference Manual.
Also pay attention, please, to the Java DSL.