/* * Copyright 2015 Evgeny Dolganov (evgenij.dolganov@gmail.com). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package och.comp.mail; import static java.util.concurrent.TimeUnit.*; import static och.api.model.PropKey.*; import static och.util.DateUtil.*; import static och.util.FileUtil.*; import static och.util.StringUtil.*; import static och.util.Util.*; import static och.util.concurrent.AsyncListener.*; import static och.util.concurrent.ExecutorsUtil.*; import java.io.File; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicLong; import javax.mail.internet.InternetAddress; import och.comp.mail.SendReq.MailMsg; import och.comp.mail.SendReq.RecipientGroup; import och.comp.mail.common.SendTask; import och.service.props.Props; import och.util.concurrent.AsyncListener; import org.apache.commons.logging.Log; public class MailService { private static final AtomicLong storeCounter = new AtomicLong(0); private Log log = getLog(getClass()); private ScheduledExecutorService workThreads = newScheduledThreadPool("MailService-send", 3); private ExecutorService answerThread = newSingleThreadExecutor("MailService-result"); private AsyncListener asyncListener; final Sender sender; Props props; private long lastStoreClearDate; public MailService(Props props) { this(null, props, null); } public MailService(Sender sender, Props props) { this(sender, props, null); } public MailService(Sender sender, Props props, AsyncListener asyncListener) { this.sender = sender != null? sender : new SenderImpl(); this.props = props; this.asyncListener = asyncListener; } public void sendAsyncWarnData(String title, Collection<String> coll){ sendAsyncWarnData(title, collectionToStr(coll, '\n')); } public void sendAsyncWarnData(String title, String data){ SendReq req = new SendReq(); req.subject("Live Chat Warning: " + title); req.text("<html><body><pre>"+data+"</pre></body></html>"); req.to(props.getStrVal(admin_Emails)); sendAsync(req); } public void sendAsyncInfoData(String title, String data){ SendReq req = new SendReq(); req.subject("Live Chat Info: " + title); req.text("<html><body><pre>"+data+"</pre></body></html>"); req.to(props.getStrVal(admin_Emails)); sendAsync(req); } public void sendAsync(SendReq req) { sendAsync(req, null); } public void sendAsync(SendReq req, SendCallback callback) { List<RecipientGroup> recipientGroups = req.getRecipientGroups(); if( recipientGroups.isEmpty()){ if(callback != null) callback.onSuccess(); return; } for (RecipientGroup recipientGroup : recipientGroups) { if(recipientGroup.isEmpty()) continue; SendTask task = createSendTask(req, recipientGroup, callback); processTaskReq(task); } } public void sendOnce(SendReq req) throws Exception{ List<RecipientGroup> recipientGroups = req.getRecipientGroups(); if(recipientGroups.isEmpty()) return; for (RecipientGroup recipientGroup : recipientGroups) { if(recipientGroup.isEmpty()) continue; SendTask task = createSendTask(req, recipientGroup, null); storeIfNeed(task); sender.send(task, props); } } private SendTask createSendTask(SendReq req, RecipientGroup recipientGroup, SendCallback callback) { MailMsg msg = req.msg; if(msg.fromEmail == null){ try { msg.fromEmail = new InternetAddress(props.getStrVal(mail_fromMail)); }catch(Exception e){ throw new IllegalStateException("can't set default from email", e); } } return new SendTask(msg, recipientGroup, callback); } protected void processTaskReq(final SendTask task) { Future<?> f = null; if(task.failCount == 0){ f = workThreads.submit(() -> trySend(task)); } else { long waitTime = props.getIntVal(mail_errorWaitDelta) * task.failCount; f = workThreads.schedule(() -> trySend(task), waitTime, MILLISECONDS); } fireAsyncEvent(asyncListener, f); } protected void trySend(SendTask task) { storeIfNeed(task); try { sender.send(task, props); successAnswerReq(task); }catch (Throwable t) { task.failCount++; if(task.failCount < props.getIntVal(mail_trySendCount)){ processTaskReq(task); } else { task.t = t; failAnswerReq(task); } } } private void storeIfNeed(SendTask task) { if( ! props.getBoolVal(mail_storeToDisc)) return; if( task.stored) return; try { File dir = new File(props.getStrVal(mail_storeDir)); dir.mkdirs(); clearOldEmailsIfNeed(dir); StringBuilder sb = new StringBuilder(); sb.append("Recipients: ").append(task.recipientGroup).append('\n'); sb.append("From: ").append(task.msg.fromEmail).append('\n'); sb.append("Subject: ").append(task.msg.subject).append('\n'); sb.append("---\n"); sb.append(task.msg.text); long storeIndex = storeCounter.incrementAndGet(); String fileName = "m-"+formatDate(new Date(), "yyyy-MM-dd_HH-mm-ss"); fileName += "_"+storeIndex; fileName += ".txt"; File file = new File(dir, fileName); file.createNewFile(); writeFileUTF8(file, sb.toString()); task.stored = true; }catch(Throwable t){ log.error("can't store email: "+t); } } private void clearOldEmailsIfNeed(File dir) { if( ! props.getBoolVal(mail_storeClearOld)) return; //once at day long curDay = dateStart(System.currentTimeMillis()); if(curDay == lastStoreClearDate) return; lastStoreClearDate = curDay; try { long lastDate = curDay - (props.getLongVal(mail_storeOldMaxDays)*24*60*60*1000); File[] files = dir.listFiles(); if(isEmpty(files)) return; for(File f : files){ if(f.isDirectory()) return; long lastModified = f.lastModified(); if(lastModified < lastDate){ f.delete(); } } }catch(Throwable t){ log.error("can't clearOldEmails: "+t); } } protected void failAnswerReq(final SendTask task) { if(task.callback == null) { log.error("can't send mail for: "+task.recipientGroup, task.t); return; } answerThread.execute(()-> { try { task.callback.onFailed(task.t); }catch (Throwable t) { log.error("error on callbak", t); } }); } protected void successAnswerReq(final SendTask task) { if(task.callback == null) return; answerThread.execute(() -> { try { task.callback.onSuccess(); }catch (Throwable t) { log.error("error on callbak", t); } }); } public void shutdown() { workThreads.shutdown(); answerThread.shutdown(); } }