Subversion Repositories SmartDukaan

Rev

Rev 35960 | Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
35956 amit 1
package com.spice.profitmandi.service.mail;
2
 
3
import com.spice.profitmandi.common.util.Utils;
4
import com.spice.profitmandi.dao.entity.mail.MailOutbox;
5
import com.spice.profitmandi.dao.entity.mail.MailOutboxAttachment;
6
import com.spice.profitmandi.dao.repository.mail.MailOutboxRepository;
7
import org.apache.logging.log4j.LogManager;
8
import org.apache.logging.log4j.Logger;
9
import org.springframework.beans.factory.annotation.Autowired;
10
import org.springframework.beans.factory.annotation.Qualifier;
11
import org.springframework.core.io.ByteArrayResource;
12
import org.springframework.core.io.InputStreamSource;
13
import org.springframework.mail.javamail.JavaMailSender;
14
import org.springframework.mail.javamail.MimeMessageHelper;
15
import org.springframework.scheduling.annotation.Async;
16
import org.springframework.stereotype.Service;
17
import org.springframework.transaction.annotation.Propagation;
18
import org.springframework.transaction.annotation.Transactional;
19
import org.springframework.transaction.support.TransactionSynchronization;
20
import org.springframework.transaction.support.TransactionSynchronizationManager;
21
 
22
import javax.mail.internet.InternetAddress;
23
import javax.mail.internet.MimeMessage;
24
import java.io.File;
25
import java.io.IOException;
26
import java.io.InputStream;
27
import java.nio.file.Files;
28
import java.time.LocalDateTime;
29
import java.util.List;
30
 
31
@Service
32
public class MailOutboxService {
33
 
34
    private static final Logger LOGGER = LogManager.getLogger(MailOutboxService.class);
35
 
36
    public static final String SENDER_SENDGRID = "SENDGRID";
37
    public static final String SENDER_GOOGLE = "GOOGLE";
38
 
39
    @Autowired
40
    private MailOutboxRepository mailOutboxRepository;
41
 
42
    @Autowired
43
    @Qualifier("mailSender")
44
    private JavaMailSender sendgridMailSender;
45
 
46
    @Autowired
47
    @Qualifier("googleMailSender")
48
    private JavaMailSender googleMailSender;
49
 
50
    // ---- Default sender (SendGrid) convenience methods ----
51
 
52
    public void queueMail(String[] emailTo, String[] cc, String subject, String body, String source) {
53
        queueMail(emailTo, cc, null, subject, body, false, source, SENDER_SENDGRID, (AttachmentData[]) null);
54
    }
55
 
56
    public void queueMail(String emailTo, String[] cc, String subject, String body, String source) {
57
        queueMail(new String[]{emailTo}, cc, null, subject, body, false, source, SENDER_SENDGRID, (AttachmentData[]) null);
58
    }
59
 
60
    public void queueMail(String[] emailTo, String[] cc, String subject, String body, boolean html, String source) {
61
        queueMail(emailTo, cc, null, subject, body, html, source, SENDER_SENDGRID, (AttachmentData[]) null);
62
    }
63
 
64
    public void queueMail(String[] emailTo, String[] cc, String[] bcc, String subject, String body, boolean html, String source) {
65
        queueMail(emailTo, cc, bcc, subject, body, html, source, SENDER_SENDGRID, (AttachmentData[]) null);
66
    }
67
 
68
    public void queueMailWithAttachments(String[] emailTo, String[] cc, String subject, String body, String source, Utils.Attachment... attachments) {
69
        queueMailWithAttachments(emailTo, cc, null, subject, body, false, source, SENDER_SENDGRID, attachments);
70
    }
71
 
72
    public void queueMailWithAttachments(String[] emailTo, String[] cc, String[] bcc, String subject, String body, boolean html, String source, Utils.Attachment... attachments) {
73
        queueMailWithAttachments(emailTo, cc, bcc, subject, body, html, source, SENDER_SENDGRID, attachments);
74
    }
75
 
76
    // ---- Google sender convenience methods ----
77
 
78
    public void queueMailViaGoogle(String[] emailTo, String[] cc, String subject, String body, String source) {
79
        queueMail(emailTo, cc, null, subject, body, false, source, SENDER_GOOGLE, (AttachmentData[]) null);
80
    }
81
 
82
    public void queueMailViaGoogle(String emailTo, String[] cc, String subject, String body, String source) {
83
        queueMail(new String[]{emailTo}, cc, null, subject, body, false, source, SENDER_GOOGLE, (AttachmentData[]) null);
84
    }
85
 
86
    public void queueMailViaGoogle(String[] emailTo, String[] cc, String subject, String body, boolean html, String source) {
87
        queueMail(emailTo, cc, null, subject, body, html, source, SENDER_GOOGLE, (AttachmentData[]) null);
88
    }
89
 
90
    public void queueMailViaGoogle(String[] emailTo, String[] cc, String[] bcc, String subject, String body, boolean html, String source) {
91
        queueMail(emailTo, cc, bcc, subject, body, html, source, SENDER_GOOGLE, (AttachmentData[]) null);
92
    }
93
 
94
    public void queueMailWithAttachmentsViaGoogle(String[] emailTo, String[] cc, String subject, String body, String source, Utils.Attachment... attachments) {
95
        queueMailWithAttachments(emailTo, cc, null, subject, body, false, source, SENDER_GOOGLE, attachments);
96
    }
97
 
98
    public void queueMailWithAttachmentsViaGoogle(String[] emailTo, String[] cc, String[] bcc, String subject, String body, boolean html, String source, Utils.Attachment... attachments) {
99
        queueMailWithAttachments(emailTo, cc, bcc, subject, body, html, source, SENDER_GOOGLE, attachments);
100
    }
101
 
102
    // ---- Internal methods ----
103
 
104
    private void queueMailWithAttachments(String[] emailTo, String[] cc, String[] bcc, String subject, String body, boolean html, String source, String senderType, Utils.Attachment... attachments) {
105
        AttachmentData[] attachmentDataArray = null;
106
        if (attachments != null && attachments.length > 0) {
107
            attachmentDataArray = new AttachmentData[attachments.length];
108
            for (int i = 0; i < attachments.length; i++) {
109
                try {
110
                    byte[] data = readInputStreamSource(attachments[i].getInputStreamSource());
111
                    attachmentDataArray[i] = new AttachmentData(attachments[i].getFileName(), data, null);
112
                } catch (IOException e) {
113
                    LOGGER.error("Failed to read attachment: {}", attachments[i].getFileName(), e);
114
                    attachmentDataArray[i] = new AttachmentData(attachments[i].getFileName(), new byte[0], null);
115
                }
116
            }
117
        }
118
        queueMail(emailTo, cc, bcc, subject, body, html, source, senderType, attachmentDataArray);
119
    }
120
 
121
    public void queueMailWithFiles(String[] emailTo, String[] cc, String[] bcc, String subject, String body, String source, File... files) {
122
        AttachmentData[] attachmentDataArray = null;
123
        if (files != null && files.length > 0) {
124
            attachmentDataArray = new AttachmentData[files.length];
125
            for (int i = 0; i < files.length; i++) {
126
                try {
127
                    byte[] data = Files.readAllBytes(files[i].toPath());
128
                    String contentType = Files.probeContentType(files[i].toPath());
129
                    attachmentDataArray[i] = new AttachmentData(files[i].getName(), data, contentType);
130
                } catch (IOException e) {
131
                    LOGGER.error("Failed to read file attachment: {}", files[i].getName(), e);
132
                    attachmentDataArray[i] = new AttachmentData(files[i].getName(), new byte[0], null);
133
                }
134
            }
135
        }
136
        queueMail(emailTo, cc, bcc, subject, body, false, source, SENDER_SENDGRID, attachmentDataArray);
137
    }
138
 
139
    /**
140
     * Core method: persists mail + attachments in current transaction, triggers async send after commit.
141
     */
142
    private void queueMail(String[] emailTo, String[] cc, String[] bcc, String subject, String body, boolean html, String source, String senderType, AttachmentData... attachments) {
143
        MailOutbox mail = new MailOutbox();
144
        mail.setEmailTo(String.join(",", emailTo));
145
        if (cc != null && cc.length > 0) {
146
            mail.setEmailCc(String.join(",", cc));
147
        }
148
        if (bcc != null && bcc.length > 0) {
149
            mail.setEmailBcc(String.join(",", bcc));
150
        }
151
        mail.setSubject(subject != null ? subject : "");
152
        mail.setBody(body != null ? body : "");
153
        mail.setHtml(html);
154
        mail.setStatus("PENDING");
155
        mail.setRetryCount(0);
156
        mail.setCreatedAt(LocalDateTime.now());
157
        mail.setSource(source);
158
        mail.setSenderType(senderType);
159
 
160
        mailOutboxRepository.persist(mail);
161
 
162
        if (attachments != null) {
163
            for (AttachmentData att : attachments) {
164
                if (att != null && att.data.length > 0) {
165
                    MailOutboxAttachment attachment = new MailOutboxAttachment();
166
                    attachment.setMailOutboxId(mail.getId());
167
                    attachment.setFileName(att.fileName);
168
                    attachment.setFileData(att.data);
169
                    attachment.setContentType(att.contentType);
170
                    mailOutboxRepository.persistAttachment(attachment);
171
                }
172
            }
173
        }
174
 
175
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
176
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
177
                @Override
178
                public void afterCommit() {
179
                    sendMailAsync(mail.getId());
180
                }
181
            });
182
        } else {
183
            // No active transaction — send immediately async
184
            sendMailAsync(mail.getId());
185
        }
186
    }
187
 
188
    @Async("mailOutboxExecutor")
189
    @Transactional(propagation = Propagation.REQUIRES_NEW)
190
    public void sendMailAsync(long mailOutboxId) {
191
        try {
192
            MailOutbox mail = mailOutboxRepository.selectPending().stream()
193
                    .filter(m -> m.getId() == mailOutboxId)
194
                    .findFirst()
195
                    .orElse(null);
196
            if (mail == null || "SENT".equals(mail.getStatus())) {
197
                return;
198
            }
199
            sendAndUpdateStatus(mail);
200
        } catch (Exception e) {
201
            LOGGER.error("Async mail send failed for mailOutboxId={}", mailOutboxId, e);
202
        }
203
    }
204
 
205
    @Transactional(propagation = Propagation.REQUIRES_NEW)
206
    public void processPendingMails() {
207
        List<MailOutbox> pendingMails = mailOutboxRepository.selectPending();
208
        LOGGER.info("Processing {} pending mails", pendingMails.size());
209
        for (MailOutbox mail : pendingMails) {
210
            sendAndUpdateStatus(mail);
211
        }
212
    }
213
 
214
    @Transactional(propagation = Propagation.REQUIRES_NEW)
215
    public void cleanupOldMails(int daysOld) {
216
        mailOutboxRepository.deleteOldSentMails(daysOld);
217
    }
218
 
219
    private void sendAndUpdateStatus(MailOutbox mail) {
220
        try {
221
            List<MailOutboxAttachment> attachments = mailOutboxRepository.selectAttachmentsByMailId(mail.getId());
222
            sendSmtp(mail, attachments);
223
            mail.setStatus("SENT");
224
            mail.setSentAt(LocalDateTime.now());
225
            mail.setErrorMessage(null);
226
        } catch (Exception e) {
227
            LOGGER.error("Failed to send mail id={}, subject={}", mail.getId(), mail.getSubject(), e);
228
            mail.setStatus("FAILED");
229
            mail.setRetryCount(mail.getRetryCount() + 1);
230
            String errorMsg = e.getMessage();
231
            if (errorMsg != null && errorMsg.length() > 1000) {
232
                errorMsg = errorMsg.substring(0, 1000);
233
            }
234
            mail.setErrorMessage(errorMsg);
235
        }
236
    }
237
 
238
    private void sendSmtp(MailOutbox mail, List<MailOutboxAttachment> attachments) throws Exception {
239
        JavaMailSender sender = resolveSender(mail.getSenderType());
240
        MimeMessage message = sender.createMimeMessage();
241
        boolean hasAttachments = attachments != null && !attachments.isEmpty();
242
        MimeMessageHelper helper = new MimeMessageHelper(message, hasAttachments);
243
 
244
        helper.setTo(mail.getEmailTo().split(","));
245
        if (mail.getEmailCc() != null && !mail.getEmailCc().isEmpty()) {
246
            helper.setCc(mail.getEmailCc().split(","));
247
        }
248
        if (mail.getEmailBcc() != null && !mail.getEmailBcc().isEmpty()) {
249
            helper.setBcc(mail.getEmailBcc().split(","));
250
        }
251
        helper.setSubject(mail.getSubject());
252
        helper.setText(mail.getBody(), mail.isHtml());
253
 
254
        String fromEmail = SENDER_GOOGLE.equals(mail.getSenderType()) ? "sdtech@smartdukaan.com" : "noreply@smartdukaan.com";
255
        String fromName = "SmartDukaan Care";
256
        helper.setFrom(new InternetAddress(fromEmail, fromName));
257
 
258
        if (hasAttachments) {
259
            for (MailOutboxAttachment att : attachments) {
260
                helper.addAttachment(att.getFileName(), new ByteArrayResource(att.getFileData()));
261
            }
262
        }
263
 
264
        sender.send(message);
265
    }
266
 
267
    private JavaMailSender resolveSender(String senderType) {
268
        if (SENDER_GOOGLE.equals(senderType)) {
269
            return googleMailSender;
270
        }
271
        return sendgridMailSender;
272
    }
273
 
274
    private byte[] readInputStreamSource(InputStreamSource source) throws IOException {
275
        try (InputStream is = source.getInputStream()) {
276
            return readAllBytes(is);
277
        }
278
    }
279
 
280
    private byte[] readAllBytes(InputStream is) throws IOException {
281
        java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
282
        byte[] buffer = new byte[8192];
283
        int len;
284
        while ((len = is.read(buffer)) != -1) {
285
            baos.write(buffer, 0, len);
286
        }
287
        return baos.toByteArray();
288
    }
289
 
290
    public static class AttachmentData {
291
        final String fileName;
292
        final byte[] data;
293
        final String contentType;
294
 
295
        public AttachmentData(String fileName, byte[] data, String contentType) {
296
            this.fileName = fileName;
297
            this.data = data;
298
            this.contentType = contentType;
299
        }
300
    }
301
}