Message not recovered in DefaultMessageListenerContainer - spring-boot

I have a factory class that I use to create a mq consumer and a DefaultMessageListenerContainer bean from that consumer that will be used for consuming messages from a topic. It goes like this -
public class MQMessageFactory {
public static DemoMessageConsumer createMessageConumer(Map<String, String> queueDetails, Map<String, String> sslDetails) throws Exception {
MQConnectionFactory mqConnectionFactory = createMQConnectionFactory(queueDetails, sslDetails);
return new DemoMessageConsumer(queueDetails, mqConnectionFactory);
}
public static MQConnectionFactory createMQConnectionFactory(Map<String, String> queue, Map<String, String> sslDetails) throws Exception {
MQConnectionFactory cf = new MQConnectionFactory();
try {
cf.setHostName(queue.get("hostname"));
cf.setPort(queue.get("port"));
cf.setQueueManager(queue.get("queueManager"));
cf.setChannel(queue.get("channel"));
cf.setTransportType(WMQConstants.WMQ_CM_CLIENT);
cf.setStringProperty(WMQConstants.USERID, queue.get("username"));
cf.setStringProperty(WMQConstants.PASSWORD, queue.get("password"));
cf.setSSLCipherSuite(queue.get("sslCipherSuite"));
cf.setSSLSocketFactory(someMethodToCreateSSLContextFactory(sslDetails));
return cf;
} catch (JMSException e) {
throw new RuntimeException("Unable to establish connection with host: " + queue.get("hostname"), e);
}
}
}
public class DemoMessageConsumer implements SessionAwareMessageListener {
private static final Logger LOGGER = LogManager.getLogger(DemoMessageConsumer.class);
private SingleConnectionFactory singleCf;
private Map<String, String> properties;
private DefaultMessageListenerContainer container;
private Consumer<Message> messageProcessor;
public DemoMessageConsumer(Map<String, String> properties, MQConnectionFactory connectionFactory) {
this.singleCf = new SingleConnectionFactory(connectionFactory);
this.singleCf.setReconnectOnException(true);
this.singleCf.afterPropertiesSet();
this.properties = properties;
}
public DefaultMessageListenerContainer listen(String queueName, Executor taskExecutor, Consumer<Message> messageProcessor) {
this.messageProcessor = messageProcessor;
this.container = new DefaultMessageListenerContainer();
this.container.setConnectionFactory(singleCf);
this.container.setDestinationName(queueName);
// this.container.setAcceptMessagesWhileStopping(true);
this.container.setSessionTransacted(true);
this.container.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);
this.container.setMessageListener(this);
this.container.setConcurrentConsumers(5);
this.container.setTaskExecutor(taskExecutor);
this.container.afterPropertiesSet();
this.container.start();
LOGGER.info("Consumer started");
return this.container;
}
#Override
public void onMessage(Message message, Session session) {
try {
LOGGER.info("Message received with MessageID: {}", message.getJMSMessageID());
this.messageProcessor.accept(message);
} catch (JMSException e) {
LOGGER.error("Error while processing the message", e);
}
}
public void triggerShutdown() {
LOGGER.info("Shutdown called");
this.container.stop();
while (this.container.isRunning()) ;
this.container.shutdown();
while (this.container.isActive()) ;
this.singleCf.destroy();
LOGGER.info("Listener is shutdown");
}
}
Further, I have a Spring boot project class where I actually create the beans and use them to listen to the queue -
#Getter
#Setter
#Configuration
#ConfigurationProperties(prefix = "ibmmq")
public class MqConsumerImpl implements ApplicationListener<ContextClosedEvent> {
private static final Logger LOGGER = LogManager.getLogger(MqConsumerImpl.class);
public Map<String, String> ssl;
public Map<String, String> queue;
#Lazy
#Autowired
#Qualifier("mqConsumer")
private DemoMessageConsumer consumer;
#Bean("mqConsumer")
public DemoMessageConsumer createConsumer() throws Exception {
return MQMessageFactory.createMessageConumer(queue, ssl);
}
#Bean("mqListener")
public DefaultMessageListenerContainer listen() {
return this.consumer.listen(queue.get("name"), Executors.newFixedThreadPool(3), message -> {
try {
LOGGER.info("{} Message reading started: {} ", Thread.currentThread().getName(), message.getBody(String.class));
// My business logic goes here
Thread.sleep(1000);
LOGGER.info("{} Message reading completed: {} ", Thread.currentThread().getName(), message.getBody(String.class));
} catch (Exception e) {
LOGGER.error(e);
}
});
}
#Override
public void onApplicationEvent(ContextClosedEvent e) {
this.consumer.triggerShutdown();
}
}
Now, I run the application and the messages are being consumer properly and everything goes well. I have a taskExecutor of three threads and all of them are being used for message consumption and task execution.
Then, I trigger a command to stop the application and one or all of the threads on which the business task were running MAY/MAY NOT throw this warning -
2020-01-14 16:29:15.110 WARN 68468 --- [pool-2-thread-3] o.s.j.l.DefaultMessageListenerContainer : Rejecting received message because of the listener container having been stopped in the meantime:
JMSMessage class: jms_text
JMSType: null
JMSDeliveryMode: 2
JMSDeliveryDelay: 0
JMSDeliveryTime: 0
JMSExpiration: 0
JMSPriority: 4
JMSMessageID: ID:414d5120484b49473033533120202020a2e4f05dce410b24
JMSTimestamp: 1578982170556
JMSCorrelationID: null
JMSDestination: queue:///QUEUE.NAME
JMSReplyTo: null
JMSRedelivered: false
JMSXAppID: demo.Application
JMSXDeliveryCount: 1
JMSXUserID: username
JMS_IBM_Character_Set: UTF-8
JMS_IBM_Encoding: 273
JMS_IBM_Format: MQSTR
JMS_IBM_MsgType: 8
JMS_IBM_PutApplType: 28
JMS_IBM_PutDate: 20200114
JMS_IBM_PutTime: 06093062
hello everyone new 1578982170555
2020-01-14 16:29:15.129 INFO 68468 --- [pool-2-thread-2] c.s.l.i.c.MqConsumerImpl : pool-2-thread-2 Message reading completed: hello everyone new 1578982170344
2020-01-14 16:29:16.180 INFO 68468 --- [ Thread-9] c.s.l.n.c.MqConsumerImpl : Listener for queue: XXXXXXXXX is shutdown
Process finished with exit code 1
Now, It is completely fine for me to see this. According to spring boot jms classes, this happens and the session rollback should be called in this case, so that when I restart my consumer, the message is redelivered. THIS IS NOT HAPPENING. I am not getting the message - hello everyone new 1578982170555 the next time I started the consumer and I got the next one. A message is hence lost without being processed. How can I safeguard it?
Note: As you can see in the logs, when this warning was raised the onMessage() method for this method was not called.

Related

Spring jms invokes the wrong listener method when receiving a message

I am playing with Spring-boot and jms message driven beans.
I installed Apache ActiveMQ.
One queue is being used on which different message types are being send and read.
One simple MessageConverter was written to convert a POJO instance into XML.
A property Class was set in the message to determine how to convert a message to a POJO:
#Component
#Slf4j
public class XMLMessageConverter implements MessageConverter {
private static final String CLASS_NAME = "Class";
private final Map<Class<?>, Marshaller> marshallers = new HashMap<>();
#SneakyThrows
private Marshaller getMarshallerForClass(Class<?> clazz) {
marshallers.putIfAbsent(clazz, JAXBContext.newInstance(clazz).createMarshaller());
Marshaller marshaller = marshallers.get(clazz);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
return marshaller;
}
#Override
public Message toMessage(#NonNull Object object, Session session) throws JMSException, MessageConversionException {
try {
Marshaller marshaller = getMarshallerForClass(object.getClass());
StringWriter stringWriter = new StringWriter();
marshaller.marshal(object, stringWriter);
TextMessage message = session.createTextMessage();
log.info("Created message\n{}", stringWriter);
message.setText(stringWriter.toString());
message.setStringProperty(CLASS_NAME, object.getClass().getCanonicalName());
return message;
} catch (JAXBException e) {
throw new MessageConversionException(e.getMessage());
}
}
#Override
public Object fromMessage(#NonNull Message message) throws JMSException, MessageConversionException {
TextMessage textMessage = (TextMessage) message;
String payload = textMessage.getText();
String className = textMessage.getStringProperty(CLASS_NAME);
log.info("Converting message with id {} and {}={}into java object.", message.getJMSMessageID(), CLASS_NAME, className);
try {
Class<?> clazz = Class.forName(className);
JAXBContext context = JAXBContext.newInstance(clazz);
return context.createUnmarshaller().unmarshal(new StringReader(payload));
} catch (JAXBException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
Messages of different type (OrderTransaction or Person) where send every 5 seconds to the queue:
#Scheduled(fixedDelay = 5000)
public void sendMessage() {
if ((int)(Math.random()*2) == 0) {
jmsTemplate.convertAndSend("DummyQueue", new OrderTransaction(new Person("Mark", "Smith"), new Person("Tom", "Smith"), BigDecimal.TEN));
}
else {
jmsTemplate.convertAndSend("DummyQueue", new Person("Mark", "Rutte"));
}
}
Two listeners were defined:
#JmsListener(destination = "DummyQueue", containerFactory = "myFactory")
public void receiveOrderTransactionMessage(OrderTransaction transaction) {
log.info("Received {}", transaction);
}
#JmsListener(destination = "DummyQueue", containerFactory = "myFactory")
public void receivePersonMessage(Person person) {
log.info("Received {}", person);
}
When I place breakpoints in the converter I see everything works fine but sometimes (not always) I get the following exception:
org.springframework.jms.listener.adapter.ListenerExecutionFailedException: Listener method could not be invoked with incoming message
Endpoint handler details:
Method [public void nl.smith.springmdb.configuration.MyListener.**receiveOrderTransactionMessage**(nl.smith.springmdb.domain.**OrderTransaction**)]
Bean [nl.smith.springmdb.configuration.MyListener#790fe82a]
; nested exception is org.springframework.messaging.converter.MessageConversionException: Cannot convert from [nl.smith.springmdb.domain.**Person**] to [nl.smith.springmdb.domain.**OrderTransaction**] for org.springframework.jms
It seems that after the conversion Spring invokes the wrong method.
I am complete in the dark why this happens.
Can somebody clarify what is happening?

Messages not rolling back on K8s pod restarts when using Spring JMS Listener with Client Ack

We have Spring JMS application ( deployed on K8s) which processes about 100 - 400 messages/sec. The application consumes messages from IBM MQ and processes them. Off late we have started noticing messages getting dropped whenever K8s pod restarts or deployments are done even though we have message ack in place. I am looking for a solution here to resolve this issue.
Software
Version
Spring Boot
2.1.7.RELEASE
IBM MQ Client
9.1.0.5
JMS
2.0.1
Java
11
#Configuration
#EnableJms
public class MqConfiguration {
#Bean
public MQConnectionFactory mqConnectionFactory(Servers configProperties) {
MQConnectionFactory mqConnectionFactory = new MQConnectionFactory();
try {
mqConnectionFactory.setHostName(configProperties.getHost());
mqConnectionFactory.setQueueManager(configProperties.getQueueManager());
mqConnectionFactory.setPort(Integer.valueOf(configProperties.getPort()));
mqConnectionFactory.setChannel(configProperties.getChannel());
mqConnectionFactory.setTransportType(WMQConstants.WMQ_CM_CLIENT);
mqConnectionFactory.setCCSID(1208);
mqConnectionFactory.setClientReconnectOptions(WMQConstants.WMQ_CLIENT_RECONNECT);
} catch (Exception e) {
logger.logError(mqConnectionFactory, ,
"Failed to create MQ ConnectionFactory", String.valueOf(HttpStatus.SC_BAD_REQUEST), e);
}
return mqConnectionFactory;
}
#Bean(name = "messageListenerContainerFactory")
public DefaultJmsListenerContainerFactory provideJmsListenerContainerFactory(
MQConnectionFactory connectionFactory) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE);
factory.setErrorHandler(new ErrorHandler() {
#Override
public void handleError(Throwable t) {
ServiceMetrics metrics = new ServiceMetrics();
metrics.setCorrelationId(UUID.getUUID());
logger.logError(factory, "Exception occured at JMS Factory Container Listener", String.valueOf(HttpStatus.SC_BAD_REQUEST), t);
}
});
return factory;
}
#Bean(name = "jmsQueueTemplate")
public JmsTemplate provideJmsQueueTemplate(MQConnectionFactory connectionFactory) {
return new JmsTemplate(connectionFactory);
}
}
#Configuration
public class AsyncConfiguration {
#Autowired
private Servers configProperties;
#Bean(name = "asyncTaskExecutor")
public ExecutorService getAsyncTaskExecutor() {
String THREAD_POOL = "th-pool-";
return getExecutor(THREAD_POOL, 70,true);
}
private ExecutorService getExecutor(String threadName, int maxPoolSize, boolean cached) {
final ThreadFactory threadFactory = new CustomizableThreadFactory(threadName);
if (cached) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(0, maxPoolSize,
60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), threadFactory);
threadPoolExecutor.setRejectedExecutionHandler((r, executor) -> {
if (!executor.isShutdown()) {
try {
executor.getQueue().put(r);
} catch (InterruptedException e) {
throw new RejectedExecutionException(e);
}
}
});
return threadPoolExecutor;
} else {
return new ThreadPoolExecutor(maxPoolSize, maxPoolSize,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(),
threadFactory);
}
}
#Component
public class InputQueueListener {
#Autowired
private ExecutorService asyncTaskExecutor;
#JmsListener(destination = "${mqserver.queue}", containerFactory = "messageListenerContainerFactory", concurrency = "1-16")
public void processXMLMessage(Message message) {
CompletableFuture.runAsync(() -> processMessage(message), asyncTaskExecutor);
}
private void processMessage(Message message) {
String inputXmlMessage = null;
boolean isSuccess = false;
try {
if (message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
inputXmlMessage = textMessage.getText();
} else if (message instanceof BytesMessage) {
BytesMessage byteMessage = (BytesMessage) message;
inputXmlMessage = CommonHelperUtil.getMessageFromBytes(byteMessage);
} else {
logger.logError(null, "Invalid message type received while converting Message to XML", String.valueOf(HttpStatus.SC_BAD_REQUEST));
errorQueuePublisher.publishErrorMessage(message);
try {
message.acknowledge();
} catch (JMSException jmsException) {
logger.logError(null, null, "Failed to Acknowledge XML message.",
String.valueOf(HttpStatus.SC_BAD_REQUEST), jmsException);
}
}
-
-
if (isSuccessProcessed) {
message.acknowledge();
} else {
message.acknowledge();
// Publishing back to the same queue
publishForRetry.publishMessageForRetry(message);
}
} catch (Exception e) {
if (StringUtils.isBlank(serviceMetrics.getCorrelationId())) {
serviceMetrics.setCorrelationId(UUID.getUUID());
}
logger.logError(null, null, "Exception while Converting Processing Message. Retrying to publish.",
String.valueOf(HttpStatus.SC_BAD_REQUEST), e);
// Publishing back to the same queue
publishForRetry.publishMessageForRetry(message);
try {
message.acknowledge();
} catch (JMSException jmsException) {
logger.logError(null, null,
"Failed to Acknowledge the Message when publishing" + "to Error Queue",
String.valueOf(HttpStatus.SC_BAD_REQUEST), jmsException);
}
}
}
}
}

Acknowledge message after JPA transaction succeed while consuming from Kafka

In a Quarkus application, I want to consume a Kafka message and persist its information in the database using an Entity Manager.
This is what I got so far:
#ApplicationScoped
public class ClientEventConsumer {
#Inject ClientRepository repository;
private final BlockingQueue<Message<ClientEvent>> messages = new LinkedBlockingQueue<>();
void startup(#Observes StartupEvent startupEvent) {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(5);
executor.scheduleAtFixedRate(() -> {
if (messages.size() > 0) {
try {
Message<ClientEvent> message = messages.take();
ClientEvent clientEvent = message.getPayload();
ClientEntity clientEntity = new ClientEntity();
clientEntity.setId(clientEvent.getId());
clientEntity.setName(clientEvent.getName());
repository.merge(clientEntity);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, 1000, 500, TimeUnit.MILLISECONDS);
}
#Incoming("qualificationCheck")
public CompletionStage<Void> consume(Message<ClientEvent> msg) {
messages.add(msg);
return msg.ack();
}
}
But with this approach the message gets acknowledged before the record is actually persisted in the database. Is there a way to only acknowledge the message if the JPA transaction succeeded?

Kafka Consumer is not receiving message in Spring Boot

My spring/java consumer is not able to access the message produced by producer. However, when I run the consumer from console/terminal it is able to receive the message produced by spring/java producer.
Consumer Configuration :
#Component
#ConfigurationProperties(prefix="kafka.consumer")
public class KafkaConsumerProperties {
private String bootstrap;
private String group;
private String topic;
public String getBootstrap() {
return bootstrap;
}
public void setBootstrap(String bootstrap) {
this.bootstrap = bootstrap;
}
public String getGroup() {
return group;
}
public void setGroup(String group) {
this.group = group;
}
public String getTopic() {
return topic;
}
public void setTopic(String topic) {
this.topic = topic;
}
}
Listener Configuration :
#Configuration
#EnableKafka
public class KafkaListenerConfig {
#Autowired
private KafkaConsumerProperties kafkaConsumerProperties;
#Bean
public Map<String, Object> getConsumerProperties() {
Map<String, Object> properties = new HashMap<>();
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaConsumerProperties.getBootstrap());
properties.put(ConsumerConfig.GROUP_ID_CONFIG, kafkaConsumerProperties.getGroup());
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true);
properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "100");
properties.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "15000");
return properties;
}
#Bean
public Deserializer stringKeyDeserializer() {
return new StringDeserializer();
}
#Bean
public Deserializer transactionJsonValueDeserializer() {
return new JsonDeserializer(Transaction.class);
}
#Bean
public ConsumerFactory<String, Transaction> consumerFactory() {
return new DefaultKafkaConsumerFactory<>(getConsumerProperties(), stringKeyDeserializer(), transactionJsonValueDeserializer());
}
#Bean
public ConcurrentKafkaListenerContainerFactory<String, Transaction> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, Transaction> factory =
new ConcurrentKafkaListenerContainerFactory<>();
factory.setConcurrency(1);
factory.setConsumerFactory(consumerFactory());
return factory;
}
}
Kafka Listener :
#Service
public class TransactionConsumer {
private static final Logger LOGGER = LoggerFactory.getLogger(Transaction.class);
#KafkaListener(topics={"transactions"}, containerFactory = "kafkaListenerContainerFactory")
public void onReceive(Transaction transaction) {
LOGGER.info("transaction = {}",transaction);
}
}
Consumer Application :
#SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
TEST CASE 1 : PASS
I started my spring/java producer and run the consumer from console. When I produce message form producer my console consumer is able to access the message.
TEST CASE 2 : FAILED
I started my spring/java consumer and run the producer from console. When I produce message form console producer my spring/java consumer is not able to access the message.
TEST CASE 3 : FAILED
I started my spring/java consumer and run the spring/java producer. When I produce message form spring/java producer my spring/java consumer is not able to access the message.
Question
Is there anything wrong in my consumer code ?
Am I missing any configuration for my kafka-listener?
Do I need to explicitly run the listener? (I don't think so since I can see on the terminal log connecting to topic, still I am not sure)
Okay you are missing AUTO_OFFSET_RESET_CONFIG in Consumer Configs
properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
auto.offset.reset
What to do when there is no initial offset in Kafka or if the current offset does not exist any more on the server (e.g. because that data has been deleted):
earliest: automatically reset the offset to the earliest offset
latest: automatically reset the offset to the latest offset
none: throw exception to the consumer if no previous offset is found for the consumer's group
anything else: throw exception to the consumer
Note : auto.offset.reset to earliest will work only if kafka does not have offset for that consumer group (So in you case need to add this property with new consumer group and restart the application)

How to send email asynchronously using spring 4 #Async

I know this question has been asked but I am not able to send email using configuration. I don't know what I am doing wrong and why I am not getting the email. Here is my Spring configuration.
#Configuration
#PropertySource(value = {
"classpath:autoalert.properties"
})
#EnableAsync
#Import({PersistenceConfig.class, EmailConfig.class, VelocityConfig.class})
#ComponentScan(basePackageClasses = {
ServiceMarker.class,
RepositoryMarker.class }
)
public class AutoAlertAppConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigInDev() {
return new PropertySourcesPlaceholderConfigurer();
}
}
Here is my email config
#Configuration
#PropertySources({
#PropertySource("classpath:email/email.properties")
})
public class EmailConfig {
#Autowired
private Environment env;
#Bean
public JavaMailSender mailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setSession(getEmailSession());
return mailSender;
}
#Bean
public MailMessage mailSettings() {
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setFrom(env.getProperty("mail.from"));
...
mailMessage.setText(env.getProperty("mail.body"));
return mailMessage;
}
private Session getEmailSession() {
Properties emailProperties = SpringUtil.loadPropertiesFileFromClassPath("email" + File.separator + "general-mail-settings.properties");
final String userName = emailProperties.getProperty("user");
final String password = emailProperties.getProperty("password");
Session session = null;
try {
session = Session.getInstance(emailProperties, new javax.mail.Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(userName, password);
}
}); //end of anonymous class
} catch (Exception e) {
e.printStackTrace();
}
return session;
} //end of getEmailSession()
}
Here is my class
public static void main(String[] args) {
try (GenericApplicationContext springContext = new AnnotationConfigApplicationContext(AutoAlertAppConfig.class)) {
AutoAlertService autoAlertService = springContext.getBean(AutoAlertServiceImpl.class);
try {
autoAlertService.handleProcess(fromDate, toDate);
} catch (Exception e) {
logger.error("Exception occurs", e);
autoAlertService.handleException(fromDate, toDate, e);
}
} catch (Exception e) {
logger.error("Exception occurs in loading Spring context: ", e);
}
#Service
public class AutoAlertServiceImpl implements AutoAlertService {
#Inject
private AsyncEmailService asyncEmailService;
#Override
public void handleProcess(String fromDate, String toDate) {
logger.info("Start process");
try {
..
//Sending email
asyncEmailService.sendMailWithFileAttachment(fromDate, toDate, file);
} catch (Exception e) {
handleException(fromDate, toDate, e);
}
logger.info("Finish process");
}
}
here is my email service
#Component
public class AsyncEmailServiceImpl implements AsyncEmailService {
#Resource(name="mailSender")
private JavaMailSender mailSender;
#Resource(name="mailSettings")
private SimpleMailMessage simpleMailMessage;
#Async
#Override
public void sendMailWithFileAttachment(String from, String to, String attachFile) {
logger.info("Start execution of async. Sending email with file attachment");
MimeMessage message = mailSender.createMimeMessage();
try{
MimeMessageHelper helper = new MimeMessageHelper(message, true);
....
helper.setText(String.format(simpleMailMessage.getText(), from, to));
FileSystemResource file = new FileSystemResource(attachFile);
helper.addAttachment(file.getFilename(), file);
mailSender.send(message);
} catch (MessagingException e) {
logger.error("Exception occurs in sending email with file attachment: " + attachFile, e);
throw new MailParseException(e);
}
logger.info("Complete execution of async. Email with file attachment " + attachFile + " send successfully.");
}
}
When I run the code it comes to method. This is printed on the console
13:59:43.004 [main] INFO com.softech.vu360.autoalert.service.impl.AutoAlertServiceImpl - Finish process
13:59:43.005 [SimpleAsyncTaskExecutor-1] INFO com.softech.vu360.autoalert.service.impl.AsyncEmailServiceImpl - Start execution of async. Sending email with file attachment
13:59:43.007 [main] INFO com.softech.vu360.autoalert.AutoAlert - Exiting application.
But I get no email. In case of Synchronous calling I get the email. Why I am not getting email ? Am I doing something wrong ?
Thanks
I think this is better approach. Create a file AsyncConfig.java
#Configuration
#EnableAsync(proxyTargetClass = true)
#EnableScheduling
public class AsyncConfig implements SchedulingConfigurer, AsyncConfigurer {
private static final Logger log = LogManager.getLogger();
private static final Logger schedulingLogger = LogManager.getLogger(log.getName() + ".[scheduling]");
#Bean
public ThreadPoolTaskScheduler taskScheduler() {
log.info("Setting up thread pool task scheduler with 20 threads.");
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(2);
scheduler.setThreadNamePrefix("task-");
scheduler.setAwaitTerminationSeconds(1200); // 20 minutes
scheduler.setWaitForTasksToCompleteOnShutdown(true);
scheduler.setErrorHandler(t -> schedulingLogger.error("Unknown error occurred while executing task.", t));
scheduler.setRejectedExecutionHandler((r, e) -> schedulingLogger.error("Execution of task {} was rejected for unknown reasons.", r));
return scheduler;
}
#Override
public Executor getAsyncExecutor() {
Executor executor = this.taskScheduler();
log.info("Configuring asynchronous method executor {}.", executor);
return executor;
}
#Override
public void configureTasks(ScheduledTaskRegistrar registrar) {
TaskScheduler scheduler = this.taskScheduler();
log.info("Configuring scheduled method executor {}.", scheduler);
registrar.setTaskScheduler(scheduler);
}
#Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler();
}
}
Now import it into your main configuration like this
#Configuration
#PropertySource(value = {
"classpath:autoalert.properties"
})
#Import({AsyncConfig.class, PersistenceConfig.class, EmailConfig.class, VelocityConfig.class})
#ComponentScan(basePackageClasses = {
ServiceMarker.class,
RepositoryMarker.class }
)
public class AutoAlertAppConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigInDev() {
return new PropertySourcesPlaceholderConfigurer();
}
}
Change return type from void to Future
#Service
public class AsyncEmailServiceImpl implements AsyncEmailService {
#Resource(name="mailSender")
private JavaMailSender mailSender;
#Resource(name="mailSettings")
private SimpleMailMessage simpleMailMessage;
#Async
#Override
public Future<String> sendMailWithFileAttachment(String from, String to, String attachFile) {
....
return new AsyncResult<String>("Attachment File successfully send: " + attachFile);
}
#Async
#Override
public Future<String> sendMail(String from, String to, String emailBody) {
....
return new AsyncResult<String>("Email send successfully");
}
}
and in my service class just do this
logger.info("Start process");
try {
....
//Sending email
Future<String> result = asyncEmailService.sendMailWithFileAttachment(fromDate, toDate, file);
} catch (Exception e) {
handleException(fromDate, toDate, e);
}
logger.info("Finish process");
See no need to check result.get(). Now when new Thread starts and application starts to finish. I configured the scheduler.setAwaitTerminationSeconds(1200); // 20 minutes in AsyncConfig.java. This will ensure that all the pending threads must complete before the application shuts down. Ofcourse this can be change according to any ones need.
Now when I run the application it prints these on the console
12:55:33.879 [main] INFO com.softech.vu360.autoalert.service.impl.AutoAlertServiceImpl - Finish process
12:55:33.895 [task-1] INFO com.softech.vu360.autoalert.service.impl.AsyncEmailServiceImpl - Start execution of async. Sending email with file attachment
12:58:09.030 [task-1] INFO com.softech.vu360.autoalert.service.impl.AsyncEmailServiceImpl - Complete execution of async. Email with file attachment D:\projects\AutoAlerts\marketo\autoalert 2015-08-24 to 2015-08-30.csv send successfully.
12:58:09.033 [main] INFO com.softech.vu360.autoalert.AutoAlert - Exiting application.
See starts new thread, but before application shuts down make sure, the thread completes and then exits the application. I configured 20 minutes for email, but as soon as thread completes, application get shut down. This is happy ending :)

Resources