I am trying to implement a function for user to download a file. The general workflow is as follows:
- User click the download button at frontend
- The backend will receive the download information from request, get data from database, and then generate zip file
- The file will be uploaded into cloud storage (Google in this case).
- An email with a signed URL will be sent to user to download the zip file from cloud.
All steps 2, 3 and 4 will be executed in a method annotated with @Async. The problem is if I restart the backend server and send only one download request, everything is fine which means the email can be received. However, it does not work after sending more download requests. No more emails will be sent. No errors, no warnings, but all the data required are received correctly, just no email was sent out.
Anyone knows what is the problem of it?
My Email Sender:
public class AbstractEmailSender { public MimeMessage mimeMessage; public MimeMessageHelper mimeMessageHelper; public JavaMailSender javaMailSender; private final String SENDER = MY_SEND_EMAIL_ADDRESS; public final String USER_NAME_KEY = "username"; public AbstractEmailSender(JavaMailSender javaMailSender) throws MessagingException { // this.javaMailSender = new JavaMailSenderImpl(); this.javaMailSender = javaMailSender; this.doInitialization(); } public void doInitialization() throws MessagingException { this.mimeMessage = this.javaMailSender.createMimeMessage(); this.mimeMessageHelper = new MimeMessageHelper(mimeMessage, true); } public void setEmailContext(String receiver, String subject) throws MessagingException { this.mimeMessageHelper.setSubject(subject); this.mimeMessageHelper.setFrom(SENDER); this.mimeMessageHelper.setTo(receiver); } } @Component public class DownloadDataSuccessEmailSender extends AbstractEmailSender implements MailService{ private final TemplateEngine templateEngine; private final static String DOWNLOAD_DATA_SUCCESS_EMAIL_SUBJECT = XXXXXX; private final static String SIGNED_URL_KEY = "signedUrl"; private final static String DOWNLOAD_EMAIL_TEMPLATE_NAME = "downloadDataSuccessEmail.html"; private static final Logger logger = LogManager.getLogger(DownloadDataSuccessEmailSender.class); public DownloadDataSuccessEmailSender(JavaMailSender javaMailSender, TemplateEngine templateEngine) throws MessagingException { super(javaMailSender); this.templateEngine = templateEngine; } @Override public void sendEmailWithSignedUrlToDownloadFile(URL signedUrl, String username, String receiver) { // print the result to make sure all data are processed correctly, nothing wrong with //this step System.out.println(signedUrl); System.out.println(username); System.out.println(receiver); try{ super.setEmailContext(receiver, DOWNLOAD_DATA_SUCCESS_EMAIL_SUBJECT); Context context = new Context(); context.setVariable(this.USER_NAME_KEY, username); context.setVariable(SIGNED_URL_KEY, signedUrl); String email = this.templateEngine.process(DOWNLOAD_EMAIL_TEMPLATE_NAME, context); this.mimeMessageHelper.setText(email, true); this.javaMailSender.send(mimeMessage); }catch (MailException | MessagingException e) { logger.error("Email with download data error: ", e); throw new EmailSendException(ErrorInfo.EMAIL_SEND_EXCEPTION.getCode(), ErrorInfo.EMAIL_SEND_EXCEPTION.getMessage()); } } }
The email configuration file:
spring: mail: host: smtp.gmail.com username: MY_EMAIL_ADDRESS password: MY_PASSWORD properties.mail.smtp: auth: true connectiontimeout: 60000 timeout: 60000 writetimeout: 50000 starttls.enable: true port: 587 protocol: smtp thymeleaf: prefix: classpath:/templates/
The async method to handle all logic
@Override @Async @Transactional(timeout = DOWNLOAD_DATA_TRANSACTION_TIME_LIMIT) public void downloadFile(SearchQuery query, DownloadRequestRecord downloadRecord){ String username = downloadRecord.getUsername(); String emailAddress = this.userService.getUserEmailAddressByUsername(username); try{ // here ignore the parts to get data from database and generate zip file and check if the file is uploaded to cloud space successfully here // ...... if (uploadFile == null) { // check if file exists logger.error("File: {} does not exist in cloud!", zipFileName); downloadRecord.setSuccess(0); this.downloadDataFailMessageEmailSender.sendEmailWithDownloadDataFail(emailAddress, username, downloadRecord.getQuery(), downloadRecord.getDownloadTime()); // send email to inform user the download is fail } else { // file exists, generate signed URL and send email with download link this.downloadDataSuccessEmailSender.sendEmailWithSignedUrlToDownloadFile(signedUrl, username, emailAddress); downloadRecord.setSuccess(1); } } catch (IOException | EmailSendException ex) { logger.error("Exception from downloading data: ", ex); downloadRecord.setSuccess(0); } finally { this.userService.recordDownloadHistoryOfUser(downloadRecord); } }
My thread pool configuration
@Configuration public class AsyncConfig implements AsyncConfigurer { private static final int CORE_POOL_SIZE = 6; private static final int MAX_POOL_SIZE = 10; private static final int QUEUE_CAPACITY = 100; private static final String THREAD_NAME_PREFIX = "ThreadPoolTaskExecutor-"; private static final Logger logger = LogManager.getLogger(AsyncConfig.class); @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(CORE_POOL_SIZE); executor.setMaxPoolSize(MAX_POOL_SIZE); executor.setQueueCapacity(QUEUE_CAPACITY); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.setThreadNamePrefix(THREAD_NAME_PREFIX); executor.initialize(); return executor; } @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return new AsyncUncaughtExceptionHandler() { @Override public void handleUncaughtException(Throwable throwable, Method method, Object... objects) { logger.error("ERROR: ", throwable); } }; } }
Advertisement
Answer
in sendEmailWithSignedUrlToDownloadFile create the MimeMessage using
MimeMessage mimeMessage = mailSender.createMimeMessage();
instead of having a single instance of MimeMessage as member of the super class