I am reading mails using spring mail inbound channel adapter once message is read i am performing some db operations in service activator of corresponding channel. My requirement is if any db operation fails adapter should read same message again.
Mail configuration :
#Bean
public DirectChannel inputChannel() {
return new DirectChannel();
}
#Bean
public IntegrationFlow pop3MailFlow() {
String url = "[url]";
return IntegrationFlows
.from(Mail.pop3InboundAdapter(url)
.javaMailProperties(p -> p.put("mail.pop3.socketFactory.class", "javax.net.ssl.SSLSocketFactory")),e -> e.autoStartup(true)
.poller(Pollers.fixedDelay(2000).transactionSynchronizationFactory(transactionSynchronizationFactory())))
.channel(inputChannel())
.handle(inboundEmailProcessor(),"messageProcess")
.get();
}
#Bean
public InboundEmailProcessor inboundEmailProcessor() {
return new InboundEmailProcessor();
}
#Bean
public TransactionSynchronizationFactory transactionSynchronizationFactory() {
TransactionSynchronizationFactory synchronizationFactory = new DefaultTransactionSynchronizationFactory(expressionEvaluatingTransactionSynchronizationProcessor());
return synchronizationFactory;
}
#Bean
public ExpressionEvaluatingTransactionSynchronizationProcessor expressionEvaluatingTransactionSynchronizationProcessor() {
ExpressionEvaluatingTransactionSynchronizationProcessor processor = new ExpressionEvaluatingTransactionSynchronizationProcessor();
ExpressionParser parser = new SpelExpressionParser();
processor.setAfterRollbackExpression(parser.parseExpression("new com.muraai.ex.config.Exp().process(payload)"));
return processor;
}
public class InboundEmailProcessor {
#Autowired
AttachmentsRepository attachmentsRepository;
#Transactional(rollbackFor = Exception.class)
public void messageProcess() {
// some db operations
// if it fails the same message should be read again
}
}
I thought this would work but its not working. Is there any way to achieve my requirement
public class Exp {
public void process(MimeMessage message) throws MessagingException {
message.setFlag(Flags.Flag.SEEN, false);
}
}
You need IMAP for that; with POP3, the server always marks them read.
You can add a spring-retry interceptor advice to the poller's advice chain and/or send the failed message to an error channel.
The retry advice can be configured for number of retries, back off policy etc.
Related
This is my JMS configuration:
#EnableJms
#Configuration
public class VmsJmsConfig implements JmsListenerConfigurer {
#Value("${spring.activemq.broker-url}")
String brokerUrl;
#Value("${spring.activemq.ssl.trustStorePath}")
String trustStorePath;
#Value("${spring.activemq.ssl.trustStorePass}")
String trustStorePass;
#Bean
public DefaultJmsListenerContainerFactory defaultJmsListenerContainerFactory(ConnectionFactory conFactory) {
DefaultJmsListenerContainerFactory defaultJmsListenerContainerFactory = new DefaultJmsListenerContainerFactory();
defaultJmsListenerContainerFactory.setConnectionFactory(conFactory);
defaultJmsListenerContainerFactory.setConcurrency("10-20");
return defaultJmsListenerContainerFactory;
}
#Bean("conFactory")
public ConnectionFactory activeMQSslConnectionFactory() throws Exception {
ActiveMQSslConnectionFactory activeMQSslConnectionFactory = new ActiveMQSslConnectionFactory(brokerUrl);
activeMQSslConnectionFactory.setTrustStore(trustStorePath);
activeMQSslConnectionFactory.setTrustStorePassword(trustStorePass);
return activeMQSslConnectionFactory;
}
#Bean
public DefaultMessageHandlerMethodFactory handlerMethodFactory() {
DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
factory.setMessageConverter(messageConverter());
return factory;
}
#Bean
public MessageConverter messageConverter() {
return new MappingJackson2MessageConverter();
}
#Override
public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
registrar.setMessageHandlerMethodFactory(handlerMethodFactory());
}
}
My app is listening to an ActiveMQ queue. It consumes a message, transforms it, sends a downstream request, waits for the response, and sends that response to another queue.
I want it to consume multiple messages at the same time and process them in parallel, but no matter how many consumer I set in setConcurrency() it only consumes 1 message at a time, and there are more than 1000 messages pending in queue.
I tried changing the concurency, but no luck. But when I comment downstream call, it consumes 10-20 messages at a time, I couldn't find reason for that.
I am trying to read a message from Solace. I am able to read message successfully, but suppose while reading/processing the message the app crashes. How can I read that message again? With my below code I am not able to read that message again. Below is my configuration:
#JmsListener(destination = "myqueue", containerFactory = "jmsContainer", concurrency = "5-10")
public void onMessage(Message msg) {
String message;
if (msg instanceof TextMessage) {
message = ((TextMessage) msg).getText();
LOG.info("In here START " + message) ;
Thread.sleep(60000); //I crash my app while thread is sleeping here
LOG.info("In here END " + msg.getJMSDestination() ) ;
}
public class SolaceConfig {
#Bean("solaceJndiTemplate")
public JndiTemplate solaceJndiTemplate() {
JndiTemplate solaceJndiTemplate = new JndiTemplate();
// setting user name /password ommitted for brevity
solaceJndiTemplate.setEnvironment(properties);
return solaceJndiTemplate;
}
#Bean
public JndiObjectFactoryBean solaceConnectionFactory(){
JndiObjectFactoryBean solaceConnectionFactory = new JndiObjectFactoryBean();
solaceConnectionFactory.setJndiTemplate(solaceJndiTemplate());
solaceConnectionFactory.setJndiName(getJndiName());
return solaceConnectionFactory;
}
#Primary
#Bean
public CachingConnectionFactory solaceCachedConnectionFactory(){
CachingConnectionFactory solaceCachedConnectionFactory = new CachingConnectionFactory();
solaceCachedConnectionFactory.setTargetConnectionFactory((ConnectionFactory)solaceConnectionFactory().getObject());
solaceCachedConnectionFactory.setSessionCacheSize(10);
return solaceCachedConnectionFactory;
}
#Bean
public JmsTemplate jmsTemplate() {
JmsTemplate jmsTemplate = new JmsTemplate(solaceCachedConnectionFactory());
jmsTemplate.setDeliveryPersistent(true);
jmsTemplate.setExplicitQosEnabled(true);
return jmsTemplate;
}
#Bean
public DefaultJmsListenerContainerFactory jmsContainer() {
DefaultJmsListenerContainerFactory container = new DefaultJmsListenerContainerFactory();
container.setConnectionFactory(solaceCachedConnectionFactory());
//container.setSessionAcknowledgeMode(Session.AUTO_ACKNOWLEDGE);
return container;
}
When using the DMLC, you should enable transactions (set sessionTransacted) so that the acknowledgment is rolled back.
Otherwise, use a SimpleMessageListenerContainer instead.
See the javadocs https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/jms/listener/DefaultMessageListenerContainer.html
It is strongly recommended to either set "sessionTransacted" to "true" or specify an external "transactionManager". See the AbstractMessageListenerContainer javadoc for details on acknowledge modes and native transaction options, as well as the AbstractPollingMessageListenerContainer javadoc for details on configuring an external transaction manager. Note that for the default "AUTO_ACKNOWLEDGE" mode, this container applies automatic message acknowledgment before listener execution, with no redelivery in case of an exception.
I have simple RabbitMQ configuration in Spring (not SpringBoot):
#Bean
public Queue queueTest() {
return QueueBuilder.durable("test")
.withArgument("x-dead-letter-exchange", "myexchange")
.withArgument("x-dead-letter-routing-key", "mykey")
.build();
}
#Bean
public Queue queueTestDlq() {
return new Queue("test.dlq", true);
}
#Bean
public Binding bindingTest(DirectExchange directExchange, Queue queueTest) {
return BindingBuilder
.bind(queueTest)
.to(directExchange)
.with("testkey");
}
#Bean
public Binding bindingTestDlq(DirectExchange directExchange, Queue queueTestDlq) {
return BindingBuilder
.bind(queueTestDlq)
.to(directExchange)
.with("testdlqkey");
}
Processing works properly and message is moved to DLQ in case of exception.
How can I append exception details (eg. message, stacktrace) to message headers for message going to DLQ?
I have the following configuration file for Spring Integration File:
#Configuration
#EnableIntegration
public class MyIntegrationConfiguration {
private static final String FILE_CHANNEL_PROCESSING = "processingfileChannel";
private static final String INTERVAL_PROCESSING = "5000";
private static final String FILE_PATTERN = "*.txt";
#Value("${import.path.source}")
private String sourceDir;
#Value("${import.path.output}")
private String outputDir;
#Bean
#InboundChannelAdapter(value = FILE_CHANNEL_PROCESSING, poller = #Poller(fixedDelay = INTERVAL_PROCESSING))
public MessageSource<File> sourceFiles() {
FileReadingMessageSource source = new FileReadingMessageSource();
source.setAutoCreateDirectory(true);
source.setDirectory(new File(sourceDir));
source.setFilter(new SimplePatternFileListFilter(FILE_PATTERN));
return source;
}
#Bean
#ServiceActivator(inputChannel = FILE_CHANNEL_PROCESSING)
public MessageHandler processedFiles() {
FileWritingMessageHandler handler = new FileWritingMessageHandler(new File(outputDir));
handler.setFileExistsMode(FileExistsMode.REPLACE);
handler.setDeleteSourceFiles(true);
handler.setExpectReply(true);
return handler;
}
#Bean
public IntegrationFlow processFileFlow() {
return IntegrationFlows
.from(FILE_CHANNEL_PROCESSING)
.transform(fileToStringTransformer())
.handle("fileProcessor", "processFile").get();
}
#Bean
public MessageChannel fileChannel() {
return new DirectChannel();
}
#Bean
public FileProcessor fileProcessor() {
return new FileProcessor();
}
#Bean
public FileToStringTransformer fileToStringTransformer() {
return new FileToStringTransformer();
}
}
For FileWritingMessageHandler from this documentation it says that if setExpectReply(true) is set:
Specify whether a reply Message is expected. If not, this handler will simply return null for a successful response or throw an Exception for a non-successful response.
My question is: where can I catch these exceptions or where can I retrieve this message/response?
The #ServiceActivator has an outputChannel attribute:
/**
* Specify the channel to which this service activator will send any replies.
* #return The channel name.
*/
String outputChannel() default "";
That's for successful replies.
Any exceptions (independently of the setExpectReply()) are just thrown to the caller. In your case the story is about an #InboundChannelAdapter. In this case the exception is caught and wrapped to the ErrorMessage to be sent to the errorChannel on the #Poller. It is a global errorChannel by default: https://docs.spring.io/spring-integration/docs/5.0.4.RELEASE/reference/html/configuration.html#namespace-errorhandler
However you have some other problem in your code. I see the second subscriber to the FILE_CHANNEL_PROCESSING. If it's not a PublishSubscribeChannel, you are going to have a round-robin distribution for messages sent to this channel. But that's already a different story. Just don't ask that question here, please!
I have successfully been able send file from one FTP Server(source) to another FTP server (target). I first send files from source to the local directory using the inbound adapter and then send files from the local directory to the target using the outbound adapter. So far this is working fine.
What I want to achieve is: to enrich the header of the message at the source with a hash code (which is generated using the file on source that is transferred) and then get that header at the target and match it with the hash code (which is generated using the file on the target)
Here is what I have tried so far:
Application.java
#SpringBootApplication
public class Application {
#Autowired
private Hashing hashing;
public static ConfigurableApplicationContext context;
public static void main(String[] args) {
context = new SpringApplicationBuilder(Application.class)
.web(false)
.run(args);
}
#Bean
#ServiceActivator(inputChannel = "ftpChannel")
public MessageHandler sourceHandler() {
return new MessageHandler() {
#Override
public void handleMessage(Message<?> message) throws MessagingException {
System.out.println("Reply channel isssss:"+message.getHeaders().getReplyChannel());
Object payload = message.getPayload();
System.out.println("Payload: " + payload);
File file = (File) payload;
// enrich header with hash code before sending to target FTP
Message<?> messageOut = MessageBuilder
.withPayload(message.getPayload())
.copyHeadersIfAbsent(message.getHeaders())
.setHeaderIfAbsent("hashCode", hashing.getHashCode(file)).build();
// send to target FTP
System.out.println("Trying to send " + file.getName() + " to target");
MyGateway gateway = context.getBean(MyGateway.class);
gateway.sendToFtp(messageOut);
}
};
}
}
FileTransferServiceConfig.java
#Configuration
#Component
public class FileTransferServiceConfig {
#Autowired
private ConfigurationService configurationService;
#Autowired
private Hashing hashing;
public static final String FILE_POLLING_DURATION = "5000";
#Bean
public SessionFactory<FTPFile> sourceFtpSessionFactory() {
DefaultFtpSessionFactory sf = new DefaultFtpSessionFactory();
sf.setHost(configurationService.getSourceHostName());
sf.setPort(Integer.parseInt(configurationService.getSourcePort()));
sf.setUsername(configurationService.getSourceUsername());
sf.setPassword(configurationService.getSourcePassword());
return new CachingSessionFactory<>(sf);
}
#Bean
public SessionFactory<FTPFile> targetFtpSessionFactory() {
DefaultFtpSessionFactory sf = new DefaultFtpSessionFactory();
sf.setHost(configurationService.getTargetHostName());
sf.setPort(Integer.parseInt(configurationService.getTargetPort()));
sf.setUsername(configurationService.getTargetUsername());
sf.setPassword(configurationService.getTargetPassword());
return new CachingSessionFactory<>(sf);
}
#MessagingGateway
public interface MyGateway {
#Gateway(requestChannel = "toFtpChannel")
void sendToFtp(Message message);
}
#Bean
public FtpInboundFileSynchronizer ftpInboundFileSynchronizer() {
FtpInboundFileSynchronizer fileSynchronizer = new FtpInboundFileSynchronizer(sourceFtpSessionFactory());
fileSynchronizer.setDeleteRemoteFiles(false);
fileSynchronizer.setRemoteDirectory(configurationService.getSourceDirectory());
fileSynchronizer.setFilter(new FtpSimplePatternFileListFilter(
configurationService.getFileMask()));
return fileSynchronizer;
}
#Bean
public AcceptOnceFileListFilter<File> acceptOnceFileListFilter() {
return new AcceptOnceFileListFilter<>();
}
#Bean
#InboundChannelAdapter(channel = "ftpChannel",
poller = #Poller(fixedDelay = FILE_POLLING_DURATION))
public MessageSource<File> ftpMessageSource() {
FtpInboundFileSynchronizingMessageSource source
= new FtpInboundFileSynchronizingMessageSource(ftpInboundFileSynchronizer());
source.setLocalDirectory(new File(configurationService.getLocalDirectory()));
source.setAutoCreateLocalDirectory(true);
source.setLocalFilter(acceptOnceFileListFilter());
return source;
}
// makes sure transfer continues on connection reset
#Bean
public Advice expressionAdvice() {
ExpressionEvaluatingRequestHandlerAdvice advice = new ExpressionEvaluatingRequestHandlerAdvice();
advice.setTrapException(true);
advice.setOnFailureExpression("#acceptOnceFileListFilter.remove(payload)");
return advice;
}
#Bean
#ServiceActivator(inputChannel = "toFtpChannel")
public void listenOutboundMessage() {
// tried to subscribe to "toFtpChannel" but this was not triggered
System.out.println("Message received");
}
#Bean
#ServiceActivator(inputChannel = "ftpChannel", adviceChain = "expressionAdvice")
public MessageHandler targetHandler() {
FtpMessageHandler handler = new FtpMessageHandler(targetFtpSessionFactory());
handler.setRemoteDirectoryExpression(new LiteralExpression(
configurationService.getTargetDirectory()));
return handler;
}
}
Hashing.java
public interface Hashing {
public String getHashCode(File payload);
}
I have managed to enrich the message in sourceHandler(), built the message and sent it to the target but I cannot figure out how I can receive that message on the target so that I can get the header from the message?
Tell me if any more information is required. I would really appreciate your help.
You have two subscribers on ftpChannel - the target handler and your sourceHandler; they will get alternate messages unless ftpChannel is declared as a pubsub channel.
There should be no problems with your subscription to toFtpChannel.
Turn on DEBUG logging to see all the subscription activity when the application context starts.
EDIT
Remove the #Bean from the #ServiceActivator - such beans must be a MessageHandler.
#ServiceActivator(inputChannel = "toFtpChannel")
public void listenOutboundMessage(Message message) {
// tried to subscribe to "toFtpChannel" but this was not triggered
System.out.println("Message received:" + message);
}
works fine for me...
Payload: /tmp/foo/baz.txt
Trying to send baz.txt to target
Message received:GenericMessage [payload=/tmp/foo/baz.txt, headers={hashCode=foo, id=410eb9a2-fe8b-ea8a-015a-d5896387cf00, timestamp=1509115006278}]
Again; you must have only one subscriber on ftpChannel unless you make it a pubsub.