How to create int-file:outbound gateway via Java configuration - java

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.

Related

Update: Spring Boot JMS static reply queue on IBM MQ Series

In my use case I need to do request-reply call to a remote system via managed queues. Using Spring Boot and IBM's MQ starter I have the problem that the application wants to create dynamic/temporary reply queues instead of using the already existing managed queue.
Configuration is set up here
#EnableJms
#Configuration
public class QueueConfiguration {
#Bean
public MQQueueConnectionFactory connectionFactory() throws JMSException {
MQQueueConnectionFactory factory = new MQQueueConnectionFactory();
factory.setTransportType(CT_WMQ); // is 1
factory.setHostName(queueProperties.getHost());
factory.setPort(queueProperties.getPort());
factory.setChannel(queueProperties.getChannel()); // combo of ${queueManager}%${channel}
return factory;
}
#Bean
public JmsMessagingTemplate messagingTemplate(ConnectionFactory connectionFactory) {
JmsMessagingTemplate jmt = new JmsMessagingTemplate(connectionFactory);
jmt.setDefaultDestinationName(queueProperties.getQueueName());
return jmt;
}
#Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setPackagesToScan("com.foo.model");
return marshaller;
}
#Bean
public MessageConverter messageConverter(Jaxb2Marshaller marshaller) {
MarshallingMessageConverter converter = new MarshallingMessageConverter();
converter.setMarshaller(marshaller);
converter.setUnmarshaller(marshaller);
return converter;
}
}
Usage is pretty straight forward: Take the object convert and send it. Wait for response receive
and convert it.
#Component
public class ExampleSenderReceiver {
#Autowired
private JmsMessagingTemplate jmsMessagingTemplate;
#Override
#SneakyThrows
public ResponseExample sendAndReceive(RequestExample request, String correlationId) {
MessagePostProcessor mpp = message -> {
message = MessageBuilder.fromMessage(message)
.setHeader(JmsHeaders.CORRELATION_ID, correlationId)
// .setHeader(JmsHeaders.REPLY_TO, "DEV.QUEUE.3") this triggers queue creation
.build();
return message;
};
String destination = Objects.requireNonNull(jmsMessagingTemplate.getDefaultDestinationName());
return jmsMessagingTemplate.convertSendAndReceive(destination, request, ResponseExample.class, mpp);
}
I read already a lot of IBM documentation and think, I need to set the message type to "MQMT_REQUEST" but I do not find the right spot to do so.
Update
Added Spring Integration as Gary proposed and added a configuration for JmsOutboundGateway
#Bean
public MessageChannel requestChannel() {
return new DirectChannel();
}
#Bean
public QueueChannel responseChannel() {
return new QueueChannel();
}
#Bean
#ServiceActivator(inputChannel = "requestChannel" )
public JmsOutboundGateway jmsOutboundGateway( ConnectionFactory connectionFactory) {
JmsOutboundGateway gateway = new JmsOutboundGateway();
gateway.setConnectionFactory(connectionFactory);
gateway.setRequestDestinationName("REQUEST");
gateway.setReplyDestinationName("RESPONSE");
gateway.setReplyChannel(responseChannel());
gateway.setCorrelationKey("JMSCorrelationID*");
gateway.setIdleReplyContainerTimeout(2, TimeUnit.SECONDS);
return gateway;
}
And adapted my ExampleSenderReceiver class
#Autowired
#Qualifier("requestChannel")
private MessageChannel requestChannel;
#Autowired
#Qualifier("responseChannel")
private QueueChannel responseChannel;
#Override
#SneakyThrows
public ResponseExample sendAndReceive(RequestExample request, String correlationId) {
String xmlContent = "the marshalled request object";
Map<String, Object> header = new HashMap<>();
header.put(JmsHeaders.CORRELATION_ID, correlationId);
GenericMessage<String> message1 = new GenericMessage<>(xmlContent, header);
requestChannel.send(message1);
log.info("send done" );
Message<?> receive = responseChannel.receive(1500);
if(null != receive){
log.info("incoming: {}", receive.toString());
}
}
The important part is gateway.setCorrelationKey("JMSCorrelationID*");
Without that line the correlationId was not propagated correct.
Next step is re-adding MessageConverters and make it nice again.
Thank you.
The default JmsTemplate (used by the JmsMessagingTemplate) always uses a temporary reply queue. You can subclass it and override doSendAndReceive(Session session, Destination destination, MessageCreator messageCreator) to use your managed queue instead.
However, it will only work if you have one request outstanding at a time (e.g. all run on a single thread). You will also have to add code for discarding "late" arrivals by checking the correlation id.
You can use async sends instead and handle replies on a listener container and correlate the replies to the requests.
Consider using spring-integration-jms and its outbound gateway instead - it has much more flexibility in reply queue handling (and does all the correlation for you).
https://docs.spring.io/spring-integration/reference/html/jms.html#jms-outbound-gateway
You are missing the queue manager.
ibm:
mq:
queueManager: QM1
channel: chanel
connName: localhost(1414)
user: admin
password: admin

How do I write a spring boot rabbitmq sender for a python receiver?

I want to write an application wherein I need a producer sending messages using spring boot rabbitmq and the receiver is written in python. The receiver part was easy-
receive.py
#!/usr/bin/env python
import pika
import sys
connection = pika.BlockingConnection(pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.exchange_declare(exchange='topic_logs',
exchange_type='topic')
result = channel.queue_declare(exclusive=True)
queue_name = result.method.queue
binding_keys = sys.argv[1:]
if not binding_keys:
sys.stderr.write("Usage: %s [binding_key]...\n" % sys.argv[0])
sys.exit(1)
for binding_key in binding_keys:
channel.queue_bind(exchange='topic_logs',
queue=queue_name,
routing_key=binding_key)
print(' [*] Waiting for logs. To exit press CTRL+C')
def callback(ch, method, properties, body):
print(" [x] %r:%r" % (method.routing_key, body))
channel.basic_consume(callback,
queue=queue_name,
no_ack=True)
channel.start_consuming()
How to write a spring boot rabbitmq sender code for this? What are the necessary things to be declared there? Please help.
Spring Boot is Java application, first of all.
So, you need to make yourself familiar with that language.
And the choice is correct: you really can send to the RabbitMQ from Java and receive from the queue in any other client.
Spring Boot provides for you RabbitTemplate bean. So, if the story is about to send you just need to inject such a bean and use its API to send:
#Autowired
RabbitTemplate rabbitTemplate;
...
this.rabbitTemplate.convertAndSend("topic_logs", binding_key, data);
See Reference Manual for more info.
With spring integration, first you need to create a configuration class:
#Configuration
public class RabbitConfig {
#Autowired
private ConnectionFactory connectionFactory;
#Bean
DirectExchange dropfileExchange() {
return new DirectExchange("exchange_name", true, false);
}
#Bean
public Queue dropfileQueue() {
return new Queue("queue_name", true);
}
#Bean
Binding dropfileExchangeBinding(DirectExchange dropfileExchange, Queue dropfileQueue) {
return BindingBuilder.bind(dropfileQueue).to(dropfileExchange).with("key_name");
}
#Bean
public RabbitTemplate dropfileExchangeTemplate() {
RabbitTemplate rt = new RabbitTemplate(connectionFactory);
rt.setExchange("exchange_name");
rt.setRoutingKey("key_name");
rt.setConnectionFactory(connectionFactory);
return rt;
}
#Bean
public RabbitMessagingTemplate rabbitMessagingTemplate() {
return new RabbitMessagingTemplate(dropfileExchangeTemplate());
}
}
Then create a gateway service:
#MessagingGateway
public interface DropfileMessageGateway {
#Gateway(requestChannel = "channel_name")
void generate(String payload);
}
And then specify the producer flow using Java DSL as follows:
#Bean
public IntegrationFlow toOutboundQueueFlow() {
return IntegrationFlows.from("channel_name")
.transform(Transformers.toJson())
.handle(Amqp.outboundAdapter(rabbitConfig.dropfileExchangeTemplate())).get();
}
That will read a message from the channel, transform it to JSON and then dispatch it using an outbound adapter to the rabbit exchange.
Note that the messages go to the channel via the messaging gateway, so the channel name in both should be the same.
Do not forget to include the appropriate dependencies.

Use #Gateway with Spring-Integration-Kafka

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.

spring amqp-outbound gateway to produce reply from a different thead (Like jms-outbound gateway)

Problem statement:
Spring amqp-outbound gateway to produce reply from a different thread (Like jms-outbound gateway, having different queue, correlate the request/response using correlation key).
Unable to correlate the message with this example.
Spring integration
<int:gateway id="outboundGateway" service-interface="com.amqp.outbound.gateway.OutboundGateway"
default-reply-channel="defaultReplyChannel" >
<int:method name="process" request-channel="inboundRequestChannel"/>
</int:gateway>
<int:channel id="defaultReplyChannel"/>
<int:channel id="inboundRequestChannel"/>
<int:channel id="enrichedInboundRequestChannel"/>
<int:channel id="processAuthRequestChannel"/>
<int:channel id="postProcessorChannel"/>
<int:chain input-channel="inboundRequestChannel" output-channel="enrichedInboundRequestChannel">
<int:service-activator id="serviceActivator"
ref="ouboundService" method="createRequest"/>
</int:chain>
<int-amqp:outbound-gateway id="outboundGtwyId" header-mapper="headerMapper"
request-channel="enrichedInboundRequestChannel"
reply-channel="defaultReplyChannel"
amqp-template="template"
reply-timeout="30000"
exchange-name="request_exchange"
routing-key="request_exchange_queue"/>
<int-amqp:inbound-channel-adapter id="amqpMessageDriven" queue-names="request_queue"
connection-factory="rabbitConnectionFactory" channel="processAuthRequestChannel"/>
<int:service-activator id="serviceActivator"
ref="ouboundService" input-channel="processAuthRequestChannel" output-channel="postProcessorChannel"
method="processRequest"/>
<int-amqp:outbound-channel-adapter amqp-template="template" channel="postProcessorChannel"
header-mapper="headerMapper" exchange-name="reply_exchange" routing-key="reply_exchange_queue"/>
<bean id="headerMapper" class="org.springframework.integration.amqp.support.DefaultAmqpHeaderMapper"/>
Config
#Bean
public RabbitTemplate template(ConnectionFactory rabbitConnectionFactory){
final RabbitTemplate template = new RabbitTemplate(rabbitConnectionFactory);
template.setQueue("reply_queue");
return template;
}
#Bean
public Binding binding(){
return BindingBuilder.bind(this.queue()).to(this.exchange()).with("request_exchange_queue");
}
#Bean
public DirectExchange exchange(){
return new DirectExchange("request_exchange");
}
#Bean
public Queue queue(){
return new Queue("request_queue", true, false, true);
}
#Bean
public Binding bindingReply(){
return BindingBuilder.bind(this.queue()).to(this.exchange()).with("reply_exchange_queue");
}
#Bean
public DirectExchange exchangeReply(){
return new DirectExchange("reply_exchange");
}
#Bean
public Queue replyQueue(){
return new Queue("reply_queue", true, false, true);
}
Service
#Service
public final class OuboundService {
public Message createRequest(String message){
System.out.println("Inside createRequest : "+ message);
final String transactionId = UUID.randomUUID().toString();
final Message builtMessage = MessageBuilder.withBody(message.getBytes())
.setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN)
.setHeader(AmqpHeaders.CORRELATION_ID, transactionId)
.build();
return builtMessage;
}
public Message processRequest(Message message){
System.out.println("Inside process Request : "+ new String(message.getBody()));
System.out.println("Header values : "+message.getMessageProperties().getHeaders());
final Message result = MessageBuilder.withBody("Successful".getBytes()).copyProperties(message.getMessageProperties())
.copyHeaders(message.getMessageProperties().getHeaders()).build();
return result;
}
}
Error:
org.springframework.integration.handler.ReplyRequiredException: No reply produced by handler 'outboundGtwyId', and its 'requiresReply' property is set to true.
GitHub source code (Resolved Solution)
https://github.com/kingkongprab/spring-amqp-outbound-gateway
The correlation is done in the Spring AMQP as well. See its RabbitTemplate#sendAndRecevie() for more info. Also there is a good documentation on the matter in the Reference Manual.
Spring Integration with its AbstractAmqpOutboundEndpoint and AmqpInboundGateway implementations provides out-of-the-box request-reply correlation solution. If you are not able to use AmqpInboundGateway on the server side, you should ensure the correlationId transfer from received request to the reply to send back. Yes, you can use dedicated exchange for replies and that is what supported by the RabbitTemplate#setQueue() to wait for replies on the client, outbound side. But that still isn't going to work without proper correlation transferring. Also see https://docs.spring.io/spring-integration/docs/4.3.12.RELEASE/reference/html/amqp.html#amqp-message-headers for the info how headers (including correlationId) are mapped in Spring Integration.
UPDATE
Thank you for sharing your application.
Well, now I see several problems:
You are definitely missing the replyQueue binding:
#Bean
public Binding bindingReply(){
return BindingBuilder.bind(this.replyQueue()).to(this.exchangeReply()).with("reply_exchange_queue");
}
RabbitTemplate must use setReplyAddress(). You have to configure MessageListenerContainer for the reply_queue and have RabbitTemplate as a listener:
#Bean
public RabbitTemplate template(ConnectionFactory rabbitConnectionFactory){
final RabbitTemplate template = new RabbitTemplate(rabbitConnectionFactory);
template.setReplyAddress(replyQueue().getName());
return template;
}
#Bean
public MessageListenerContainer replyContainer(RabbitTemplate template) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(template.getConnectionFactory());
container.setQueues(replyQueue());
container.setMessageListener(template);
return container;
}
Your OuboundService with org.springframework.amqp.core.Message manipulation is useless. The Channel Adapters don't know about this type of payload and your custom Message just becomes as a serialized body of another org.springframework.amqp.core.Message. I have changed it to this and everything works well:
public String createRequest(String message){
System.out.println("Inside createRequest : "+ message);
return message;
}
public Message processRequest(Message message){
System.out.println("Inside process Request : " + message);
return message;
}
Anyway I suggest you to rethink your design and come back to the AmqpInboundGateway.
BTW in the final solution you don't need to care about any correlation. The Framework does that for you automatically.

Spring Integration Java DSL - Set RecepientListRouter recipients dynamically?

I have a method which needs to execute multiple tasks async'ly.. I've managed to achieve that using the following IntegrationFlow:
#Bean
public IntegrationFlow startJobTask() {
return IntegrationFlows.from("TaskRoutingChannel")
.handle("jobService", "executeTasks")
.routeToRecipients(r -> r
.recipient("testTaskChannel")
.recipient("test2TaskChannel"))
.get();
}
#Bean ExecutorChannel testTaskChannel(){
return new ExecutorChannel(this.getAsyncExecutor());
}
#Bean ExecutorChannel test2TaskChannel(){
return new ExecutorChannel(this.getAsyncExecutor());
}
#Bean
public Executor getAsyncExecutor() {
SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor();
return executor;
}
#Bean
public IntegrationFlow testTaskFlow() {
return IntegrationFlows.from("testTaskChannel")
.handle("testTaskService", "executeAsync")
.get();
}
#Bean
public IntegrationFlow test2TaskFlow() {
return IntegrationFlows.from("test2TaskChannel")
.handle("test2TaskService", "executeAsync")
.get();
}
The above flow is basically as follows:
TaskRoutingChannel calls a serviceActivator method executeTasks
executeTasks returns something like Message<List<myTask>>
this Message is routed to channels testTaskChannel and test2TaskChannel
which calls their own async serviceActivator methods.
Now, the issue is that I don't want to hardcode the recipient channels.
I could avoid hardcoding with normal router by setting the destination channel as a header. However, recepientListRouters don't seem to have the capability to get recipient channel name using expressions.
Is there any way to set the recipient channels dynamically?
Sorry for delay first of all.
Actually the regular .route() can do that for you, because it has exactly has this option:
protected abstract Collection<MessageChannel> determineTargetChannels(Message<?> message);
So, if your header extraction returns the list of channels, the HeaderValueRouter will be able to the message to all of them.

Categories

Resources