I want to send an email once an SFTP upload is completed ...
I have an SFTP uploader:
<sftp:outbound-channel-adapter
id="sftpOutboundAdapter"
channel="inputFiles"
charset="UTF-8"
remote-directory="${directory.remote}"
session-factory="sftpSessionFactory">
<sftp:request-handler-advice-chain>
<bean class="org.springframework.integration.handler.advice.ExpressionEvaluatingRequestHandlerAdvice">
<property name="onSuccessExpression" value="payload.delete()"/>
<property name="successChannel" ref="successChannel"/>
<property name="onFailureExpression" value="payload.renameTo(payload.absolutePath + '.error')"/>
<property name="failureChannel" ref="failChannel"/>
</bean>
</sftp:request-handler-advice-chain>
</sftp:outbound-channel-adapter>
Which sends the payload to the successChannel once it's done:
<int:channel id="successChannel">
<int:interceptors>
<int:wire-tap channel="successLogChannel"/>
</int:interceptors>
</int:channel>
... which then logs it thanks to a wiretap.
<int:channel id="successLogChannel"/>
<int:transformer input-channel="successLogChannel" output-channel="logChannel"
expression="'Successfully transferred ' + inputMessage.payload.absolutePath + ' [result=' + payload + ']'"/>
<int:logging-channel-adapter id="logChannel" level="INFO"/>
And here is where it's breaking, I don't think properties is the right element to use, I basically need to break out into a java class and be able to pass in parameters including the name of the file I just uploaded.
<int:service-activator input-channel="successChannel" ref="Email" method="sendSuccessMail">
<property name="to" value="test#gmail.com" />
<property name="from" value="me#me.com" />
<property name="filename" ref="'inputMessage.payload.absolutePath'" />
</int:service-activator>
Java Class:
#Component(value = "Email")
public class Email extends AbstractMessaging<EmailRequest, EmailResponse> {
....
public void sendSuccessMail(String to, String from, String filename){
log.info("--------------------------------------------");
log.info("Success Mail will be sent here to" + to + ", from " + from + " for " + filename);
log.info("--------------------------------------------");
}
}
FactoryBean threw exception on object creation; nested exception is
java.lang.IllegalArgumentException: Target object of type [class ...
.email.Email] has no eligible methods for handling Messages.
Is it possible to pass params or just the payload if I can't params to my method sendSuccessMail?
Use a <header-enricher/> to create the values you need in headers; then...
public void sendSuccessMail(#Header("to") String to, #Header("from") String from, #Payload String filename){
...
}
<int:chain ...>
<int:header-enricher>
<int:header name="to" value="test#gmail.com">
...
<int:header name="inputMessage" expression="inputMessage">
</int:header-enricher>
<int:transformer expression="headers['inputMessage'].payload.absolutePath">
<int:service-activator ... />
</int:chain>
Related
Migrating existing legacy application to java 11 and spring 5 and seems to be ServiceActivator method not invoking. I see publisher method invoking and printing log statement but no logs printing from ServiceActivator class
spring 5.3.14
spring-integration-* 5.5.7
XML Configuration:
<int:channel id="employeeServicesChannel">
<int:dispatcher task-executor="employeeServicesExecutor" />
</int:channel>
<bean id="employeeServicesExecutor"
class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="10" />
<property name="maxPoolSize" value="40" />
<property name="queueCapacity" value="10" />
<property name="keepAliveSeconds" value="0"></property>
</bean>
public class EmployeeServicesPublisher {
#Publisher(channel = "employeeServicesChannel")
public EmployeeServicesDto publishEmployeeServicesRequest(EmployeeServicesDto requestDto) {
logger.info(" employee service request :" + requestDto);
return requestDto;
}
#MessageEndpoint
public class EmployeeServicesServerGateway {
#ServiceActivator(inputChannel = "employeeServicesChannel", outputChannel =
"esDynamicOutputChannel")
public EmployeeServicesDto processRequest(EmployeeServicesDto employeeServicesDto) {
logger.info("===================================");
logger.info(" request Id:" + employeeServicesDto.getRequestId());
return employeeServicesDto;
}
[EDIT] Uploading complete configs:
rabbit.xml which dequeues from rabbit
<rabbit:connection-factory id="amqpConnectionFactoryInbound"
host="${rabbit.host}" port="${rabbit.port}"
username="${rabbit.username}" password="${rabbit.password}" channel-
cache-size="5"
connection-factory="rabbitConnectionFactoryInbound"/>
<beans:bean id="rabbitConnectionFactoryInbound"
class="com.rabbitmq.client.ConnectionFactory">
<beans:property name="requestedHeartbeat"
value="60" />
</beans:bean>
<!-- Inbound Adapter to AMQP RabbitMq and write to file -->
<int-amqp:inbound-channel-adapter id="rabbitMQInboundChannelAdapter"
channel="rabbitInboundMessageChannel"
concurrent-consumers="8" task-
executor="rabbit-executor" connection-
factory="amqpConnectionFactoryInbound"
message-converter="byteArrayToStringConverter" queue-
names="${rabbit.queue}" acknowledge-mode="MANUAL" error-
channel="errorChannelId"
prefetch-count="25" />
<header-enricher input-channel="rabbitInboundMessageChannel" output-
channel="rabbitOutboundboundMessageChannel">
<int:header name="Operation" value="${operation.rabbit}" />
<int:header name="GUID" expression="#{
'T(java.util.UUID).randomUUID().toString()' }" />
<int:header name="operationStartTime" expression="#{
'T(java.lang.System).currentTimeMillis()' }" />
</header-enricher>
<int:channel id="rabbitOutboundboundMessageChannel">
<int:interceptors>
<int:wire-tap channel="loggerChannel" />
</int:interceptors>
</int:channel>
<task:executor id="rabbit-executor" rejection-policy="CALLER_RUNS"
pool-size="10-30"
queue-capacity="25" />
</beans:beans>
The message is then sent to router channel: router.xml
<int:header-enricher input-channel="rabbitOutboundboundMessageChannel"
output-channel="routerChannel">
<int:header name="Operation" value="${operation.router}"
overwrite="true" />
<int:header name="file_name" expression="headers['GUID'] + '.xml'" />
<int:header name="operationStartTime" expression="#{
'T(java.lang.System).currentTimeMillis()' }"
overwrite="true" />
<int:error-channel ref="errorChannelId" />
</int:header-enricher>
<int:recipient-list-router id="rabbitMsgrouter" input-
channel="routerChannel">
<int:recipient channel="fileBackupChannel" selector-expression="new
String(payload).length()>0" />
<int:recipient channel="transformerChannel" />
</int:recipient-list-router>
<int:channel id="transformerChannel">
<int:interceptors>
<int:wire-tap channel="loggerChannel" />
</int:interceptors>
</int:channel>
<int:channel id="fileBackupChannel"/>
<int:channel id="loggerChannel"/>
</beans>
The message is now sent to persister.xml and transformer.xml. The following is persister.xml and I want to ack if persistence is successful. There are other downstream processes after transformer.xml
<int:header-enricher input-channel="fileBackupChannel" output-
channel="fileSaveChannel">
<int:header name="Operation" value="${operation.filePersister}"
overwrite="true" />
<int:header name="replyChannel" value="nullChannel" />
<int:header name="operationStartTime" expression="#{
'T(java.lang.System).currentTimeMillis()' }" />
<int:error-channel ref="errorChannelId" />
</int:header-enricher>
<int-file:outbound-gateway id="fileBackUpChannelAdapter"
directory="${file.location}"
request-channel="fileSaveChannel" reply-channel="rabbitAckChannel"/>
<int:service-activator input-channel="rabbitAckChannel" output-
channel="nullChannel" ref="ackRabbit" method="handleRabbitAcks" />
<bean id="ackRabbit"
class="com.expedia.dataloader.rabbit.RabbitAcknowledgement"/>
<int:channel id="rabbitAckChannel">
<int:interceptors>
<int:wire-tap channel="loggerChannel" />
</int:interceptors>
</int:channel>
<int:channel id="loggerChannel"/>
<int:channel id="fileSaveChannel"/>
</beans>
I'm having trouble manually acking payloads from rabbitmq.
This is my work flow:
1. Get message from rabbit using inbound-channel-adapter:
<int-amqp:inbound-channel-adapter id="rabbitMQInboundChannelAdapter"
channel="rabbitInboundMessageChannel"
concurrent-consumers="${rabbit.concurrentConsumers}" task-
executor="rabbit-executor" connection-
factory="amqpConnectionFactoryInbound"
message-converter="byteArrayToStringConverter" queue-
names="${rabbit.queue}" acknowledge-mode="MANUAL" error-
channel="errorChannelId"
prefetch-count="${rabbit.prefetchCount}" />
2. Persist message to disk using outbound-gateway:
<int-file:outbound-gateway id="fileBackUpChannelAdapter"
directory="${file.location}"
request-channel="fileSaveChannel" reply-channel="loggerChannel" />
3. ack from rabbit when persister (step 2) succeeds.
for step (3), i wrote the following code:
public class RabbitAcknowledgement {
public void handleRabbitAcks(Message<?> message) throws IOException {
com.rabbitmq.client.Channel channel = (Channel)
message.getHeaders().get("amqp_channel");
long deliveryTag = (long) message.getHeaders().get("amqp_deliveryTag");
channel.basicAck(deliveryTag, false);
}
which I'm calling from spring via:
<int:service-activator input-
channel="rabbitOutboundboundMessageChannel" output-
channel="routerChannel" ref="ackRabbit" method="handleRabbitAcks" />
This doesn't work and the the rabbit payloads in my queue are not acked.
My questions are:
Do I need MANUAL ack in this scenario?
What am I doing wrong?
It should work fine; I just ran a quick test and it works for me...
#SpringBootApplication
public class So44666444Application implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(So44666444Application.class, args).close();
}
#Autowired
private RabbitTemplate template;
private final CountDownLatch latch = new CountDownLatch(1);
#Override
public void run(String... args) throws Exception {
this.template.convertAndSend("foo", "bar");
latch.await();
}
#Bean
public AmqpInboundChannelAdapter adapter(ConnectionFactory cf) {
AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(listenerContainer(cf));
adapter.setOutputChannelName("ack");
return adapter;
}
#Bean
public AbstractMessageListenerContainer listenerContainer(ConnectionFactory cf) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(cf);
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
container.setQueueNames("foo");
return container;
}
#ServiceActivator(inputChannel = "ack")
public void ack(#Header(AmqpHeaders.CHANNEL) Channel channel, #Header(AmqpHeaders.DELIVERY_TAG) Long tag)
throws IOException {
System.out.println("Acking: " + tag);
channel.basicAck(tag, false);
latch.countDown();
}
}
If I set a breakpoint on the basicAck, I see the message as unacked on the console; stepping over to the next line and the message is removed.
am trying to implement Spring RabbitMq with XML based instead of configuration file. The message is sent ( I can see it in the RabbitMQ Admin Management ) but the consumer is not printing any output. Consumer is initialized ( can confirm that the constructor is called ). Can you please let me know what is the problem with the below:
rabbitConfiguration.xml:
<rabbit:connection-factory id="connectionFactory" host="localhost" username="guest" password="guest" />
<rabbit:template id="amqpTemplate" connection-factory="connectionFactory" exchange="my.exchange" routing-key="my.test.1" />
<rabbit:admin connection-factory="connectionFactory" />
<rabbit:queue id="myQueue" name="my.queue" />
<rabbit:topic-exchange id="myExchange" name="my.exchange">
<rabbit:bindings>
<rabbit:binding queue="myQueue" pattern="my.#.*">
</rabbit:binding>
</rabbit:bindings>
</rabbit:topic-exchange>
<rabbit:listener-container id="myListenerContainer" connection-factory="connectionFactory" >
<rabbit:listener ref="aListener" method="printer" queues="myQueue"/>
</rabbit:listener-container>
<bean id="aListener" class="rabbitmq.MyReceiver" />
rabbitmq.MyReceiver.java
Public class MyReceiver {
public MyReceiver() {
System.out.println("init..");
}
public void printer(String msg){
System.out.println("message: " + msg);
}
}
Here is Producer code in different Class:
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("rabbitConfiguration.xml");//loading beans
RabbitTemplate rb = (RabbitTemplate) context.getBean("amqpTemplate");
rb.convertAndSend("blah balh");
context.close();
Version : spring-integration-core - 2.2.3
Here is the simplified version of my splitter/aggregator setup.
<task:executor id="taskExecutor" pool-size="${pool.size}"
queue-capacity="${queue.capacity}"
rejection-policy="CALLER_RUNS" keep-alive="120"/>
<int:channel id="service-requests"/>
<int:channel id="service-request"/>
<int:channel id="channel-1">
<int:dispatcher task-executor="taskExecutor" failover="false"/>
</int:channel>
<int:channel id="channel-2">
<int:dispatcher task-executor="taskExecutor" failover="false"/>
</int:channel>
<int:gateway id="myServiceRequestor" default-reply-timeout="${reply.timeout}"
default-reply-channel="service-aggregated-reply"
default-request-channel="service-request"
service-interface="com.blah.blah.MyServiceRequestor"/>
<int:splitter input-channel="service-request"
ref="serviceSplitter" output-channel="service-requests"/>
<!-- To split the request and return a java.util.Collection of Type1 and Type2 -->
<bean id="serviceSplitter" class="com.blah.blah.ServiceSplitter"/>
<int:payload-type-router input-channel="service-requests" resolution-required="true">
<int:mapping
type="com.blah.blah.Type1"
channel="channel-1"/>
<int:mapping
type="com.blah.blah.Type2"
channel="channel-2"/>
</int:payload-type-router>
<!-- myService is a bean where processType1 & processType2 method is there to process the payload -->
<int:service-activator input-channel="channel-1"
method="processType1" output-channel="service-reply" requires-reply="true"
ref="myService"/>
<int:service-activator input-channel="channel-2"
method="processType2" output-channel="service-reply" requires-reply="true"
ref="myService"/>
<int:publish-subscribe-channel id="service-reply" task-executor="taskExecutor"/>
<!-- myServiceAggregator has a aggregate method which takes a Collection as argument(aggregated response from myService) -->
<int:aggregator input-channel="service-reply"
method="aggregate" ref="myServiceAggregator"
output-channel="service-aggregated-reply"
send-partial-result-on-expiry="false"
message-store="myResultMessageStore"
expire-groups-upon-completion="true"/>
<bean id="myResultMessageStore" class="org.springframework.integration.store.SimpleMessageStore" />
<bean id="myResultMessageStoreReaper" class="org.springframework.integration.store.MessageGroupStoreReaper">
<property name="messageGroupStore" ref="myResultMessageStore" />
<property name="timeout" value="2000" />
</bean>
<task:scheduled-tasks>
<task:scheduled ref="myResultMessageStoreReaper" method="run" fixed-rate="10000" />
</task:scheduled-tasks>
If the processType1/processType2 method in mySevice throws a RuntimeException, then it tries to send the message to an error channel(i believe spring does it by default) and the message payload in error channel stays on in heap and not getting garbage collected.
Updated More Info:
For my comment on error channel. I debugged the code and found that ErrorHandlingTaskExecutor is trying to use a MessagePublishingErrorHandler which inturn sending the message to the channel returned by MessagePublishingErrorHandler.resolveErrorChannel method.
Code snippet from ErrorHandlingTaskExecutor.java
public void execute(final Runnable task) {
this.executor.execute(new Runnable() {
public void run() {
try {
task.run();
}
catch (Throwable t) {
errorHandler.handleError(t); /// This is the part which sends the message in to error channel.
}
}
});
}
Code snipper from MessagePublishingErrorHandler.java
public final void handleError(Throwable t) {
MessageChannel errorChannel = this.resolveErrorChannel(t);
boolean sent = false;
if (errorChannel != null) {
try {
if (this.sendTimeout >= 0) {
sent = errorChannel.send(new ErrorMessage(t), this.sendTimeout);
.....
When i take a heap dump, I always see the reference to the payload message(which i believe is maintained in the above channel) and not getting GC'ed.
Would like to know what is the correct way to handle this case or if i'm missing any in my config?
Also is it possible to tell spring to discard the payload(instead of sending it to error channel) in case of any exception thrown by the service activator method?
Looking forward for your inputs.
Thanks.
You don't have an error-channel defined on your gateway so we won't send it there, we'll just throw an exception to the caller.
However, the partial group is sitting in the aggregator and will never complete. You need to configure a MessageGroupStoreReaper as shown in the reference manual (or set a group-timeout in Spring Integration 4.0.x) to discard the partial group.
The workflow of my application is adding a message header, then routing the message depending on another header and sending the message further downstream. My tests for this workflow succeed individually but not when executed together, although it seems that I have reset all relevant objects.
The spring integration workflow:
<int:header-enricher input-channel="workflowStart" output-channel="headerEnriched">
<int:header name="correlationId" expression="headers.id.toString()" />
</int:header-enricher>
<int:header-value-router input-channel="headerEnriched" header-name="messageType">
<int:mapping value="stp" channel="stpChannel" />
<int:mapping value="nonStp" channel="nonStpChannel" />
</int:header-value-router>
<int:publish-subscribe-channel id="nonStpChannel" />
<int:publish-subscribe-channel id="stpChannel" apply-sequence="true"/>
<int:chain input-channel="nonStpChannel" output-channel="systemChannel1" id="nonStpChainBlockA">
<!-- do something -->
</int:chain>
<int:chain input-channel="stpChannel" output-channel="systemChannel3" id="stpChainBlockA">
<!-- do something -->
</int:chain>
The Java test class:
#Autowired
private MessageChannel workflowStart;
#Autowired
private MessageHandler systemChannel1OutputHandler;
#Autowired
private MessageHandler systemChannel3OutputHandler;
#Value("/xml/tradeStp.xml")
private Resource stpMessageResource;
private String stpMessage;
#Value("/xml/tradeNonStp.xml")
private Resource nonStpMessageResource;
private String nonStpMessage;
#Before
public void setup() throws Exception {
reset(systemChannel1OutputHandler);
reset(systemChannel3OutputHandler);
stpMessage = IOUtils.toString(stpMessageResource.getInputStream());
nonStpMessage = IOUtils.toString(nonStpMessageResource.getInputStream());
}
#Test
public void nonStpMessageWorkflow1Test() {
Message<String> m = MessageBuilder
.withPayload(nonStpMessage)
.setHeaderIfAbsent("messageType", "nonStp")
.build();
workflowStart.send(m);
verify(systemChannel1OutputHandler, times(1)).handleMessage(any(Message.class));
}
#Test
public void stpMessageWorkflow2Test() {
Message<String> m = MessageBuilder
.withPayload(stpMessage)
.setHeaderIfAbsent("messageType", "stp")
.build();
workflowStart.send(m);
verify(systemChannel3OutputHandler, times(1)).handleMessage(any(Message.class));
}
And the test context:
<import resource="classpath:/spring/workflow.xml"/>
<int:outbound-channel-adapter channel="systemChannel1" ref="systemChannel1OutputHandler" method="handleMessage"/>
<bean id="systemChannel1OutputHandler" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.springframework.integration.core.MessageHandler"/>
</bean>
<int:outbound-channel-adapter channel="systemChannel3" ref="systemChannel3OutputHandler" method="handleMessage"/>
<bean id="systemChannel3OutputHandler" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.springframework.integration.core.MessageHandler"/>
</bean>
<int:outbound-channel-adapter channel="headerEnriched" ref="headerEnricherOutputHandler" method="handleMessage"/>
<bean id="headerEnricherOutputHandler" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.springframework.integration.core.MessageHandler"/>
</bean>
If I run both tests together, the first test succeeds, while the second one fails with the message:
Wanted but not invoked:
messageHandler.handleMessage(<any>);
-> at com.company.integration.com.company.export.config.InterfaceTest.stpMessageWorkflow2Test(InterfaceTest.java:81)
Actually, there were zero interactions with this mock.
I have tried to debug in various ways. The problem seems to be just after the header-value-router and before the chain. The last debug output for the second test is a "postSend" on the headerEnriched channel.
Any advice is very welcome.
UPDATE:
I failed to include an additional outputHandler that I have in my context for another test, which I have included now. By removing the headerEnricherOutputHandler all tests run fine. However, I still don't understand the reason why this causes problems when tests are being run together.
I've had problems with Mockito using any(class). Where you have
verify(systemChannel3OutputHandler, times(1)).handleMessage(any(Message.class));
does it work with if you remove the class, like:
verify(systemChannel3OutputHandler, times(1)).handleMessage(any());
I cannot reproduce your issue with the following, which does not appear materially different to your tests...
<int:header-value-router input-channel="headerEnriched" header-name="messageType">
<int:mapping value="stp" channel="stpChannel" />
<int:mapping value="nonStp" channel="nonStpChannel" />
</int:header-value-router>
<int:publish-subscribe-channel id="nonStpChannel" />
<int:publish-subscribe-channel id="stpChannel" apply-sequence="true"/>
<int:chain input-channel="stpChannel" output-channel="systemChannel1">
<int:transformer expression="'bar'"/>
</int:chain>
<int:chain input-channel="nonStpChannel" output-channel="systemChannel3">
<int:transformer expression="'bar'"/>
</int:chain>
<int:channel id="systemChannel1" />
<int:channel id="systemChannel3" />
<int:outbound-channel-adapter channel="systemChannel1" ref="systemChannel1OutputHandler" method="handleMessage"/>
<bean id="systemChannel1OutputHandler" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.springframework.integration.core.MessageHandler"/>
</bean>
<int:outbound-channel-adapter channel="systemChannel3" ref="systemChannel3OutputHandler" method="handleMessage"/>
<bean id="systemChannel3OutputHandler" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.springframework.integration.core.MessageHandler"/>
</bean>
.
#ContextConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
public class Foo {
#Autowired
private MessageChannel headerEnriched;
#Autowired
private MessageHandler systemChannel1OutputHandler;
#Autowired
private MessageHandler systemChannel3OutputHandler;
#Test
public void test10() {
Message<String> foo = MessageBuilder.withPayload("foo")
.setHeader("messageType", "stp").build();
headerEnriched.send(foo);
verify(systemChannel1OutputHandler).handleMessage(any(Message.class));
}
#Test
public void test30() {
Message<String> foo = MessageBuilder.withPayload("foo")
.setHeader("messageType", "nonStp").build();
headerEnriched.send(foo);
verify(systemChannel3OutputHandler).handleMessage(any(Message.class));
}
}
Both tests run fine for me.