Subversion Repositories SmartDukaan

Rev

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