package com.robonobo.midas; import java.io.*; import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javax.servlet.ServletContext; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.commons.mail.HtmlEmail; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.context.ServletContextAware; import com.robonobo.common.util.TextUtil; import com.robonobo.core.api.model.Playlist; import com.robonobo.midas.dao.PlaylistDao; import com.robonobo.midas.dao.UserDao; import com.robonobo.midas.model.*; import com.robonobo.remote.service.MidasService; import freemarker.cache.FileTemplateLoader; import freemarker.template.*; @Service("message") @SuppressWarnings({ "rawtypes", "unchecked" }) public class MessageServiceImpl implements MessageService, InitializingBean, DisposableBean, ServletContextAware { private String smtpHost; private int smtpPort; private boolean useTls; private String smtpUser; private String smtpPwd; private String rbnbUrl; private Configuration freemarkerCfg; private ExecutorService executor; Log log = LogFactory.getLog(getClass()); @Autowired AppConfig appConfig; @Autowired MidasService midas; @Autowired UserDao userDao; @Autowired PlaylistDao playlistDao; public MessageServiceImpl() { } @Override public void afterPropertiesSet() throws Exception { smtpHost = appConfig.getInitParam("smtpHost"); if (smtpHost == null) { log.error("SMTP host not set - no mails will be sent!"); return; } smtpPort = Integer.parseInt(appConfig.getInitParam("smtpPort")); useTls = Boolean.parseBoolean(appConfig.getInitParam("smtpUseTls")); smtpUser = appConfig.getInitParam("smtpUser"); smtpPwd = appConfig.getInitParam("smtpPwd"); rbnbUrl = appConfig.getInitParam("rbnbUrl"); log.warn("Starting mail threadpool"); executor = Executors.newFixedThreadPool(8); } @Override public void setServletContext(ServletContext sc) { // Set up freemarker freemarkerCfg = new Configuration(); freemarkerCfg.setServletContextForTemplateLoading(sc, "WEB-INF/freemarker"); freemarkerCfg.setObjectWrapper(new DefaultObjectWrapper()); } @Override public void destroy() throws Exception { log.warn("Shutting down mail threadpool"); executor.shutdown(); log.warn("Mail threadpool shutdown"); } private Map newModel(String toName, String toEmail) { Map result = new HashMap(); result.put("rbnbUrl", rbnbUrl); result.put("toName", toName); result.put("toEmail", toEmail); return result; } @Override public void sendWelcome(MidasUser newUser) throws IOException { Map model = newModel(newUser.getFriendlyName(), newUser.getEmail()); model.put("toUser", newUser); sendMail(null, null, newUser.getEmail(), newUser.getFriendlyName(), newUser.getFriendlyName() + ", welcome to robonobo", "welcome", model); } @Override public void sendFriendRequest(MidasUser requestor, MidasUser requestee, MidasPlaylist p) throws IOException { MidasFriendRequest friendReq = midas.createOrUpdateFriendRequest(requestor, requestee, p); Map model = newModel(requestee.getFriendlyName(), requestee.getEmail()); model.put("fromUser", requestor); model.put("acceptUrl", rbnbUrl + "friendrequest/" + friendReq.getRequestCode()); model.put("playlist", p); sendMail(requestor.getEmail(), requestor.getFriendlyName(), requestee.getEmail(), requestee.getFriendlyName(), requestor.getFriendlyName() + " would like to be your friend on robonobo", "friendrequest", model); } @Override public MidasInvite sendInvite(MidasUser fromUser, String toEmail, MidasPlaylist p) throws IOException { MidasInvite invite = midas.createOrUpdateInvite(toEmail, fromUser, p); Map model = newModel(null, toEmail); model.put("fromUser", fromUser); model.put("inviteUrl", rbnbUrl + "invite/" + invite.getInviteCode()); model.put("playlist", p); sendMail(fromUser.getEmail(), fromUser.getFriendlyName(), toEmail, null, fromUser.getFriendlyName() + " has invited you to robonobo", "invite", model); return invite; } @Override public void sendPlaylistShare(MidasUser fromUser, MidasUser toUser, Playlist p) throws IOException { Map model = newModel(toUser.getFriendlyName(), toUser.getEmail()); model.put("fromUser", fromUser); model.put("playlist", p); model.put("playlistUrl", playlistUrl(p)); sendMail(fromUser.getEmail(), fromUser.getFriendlyName(), toUser.getEmail(), toUser.getFriendlyName(), fromUser.getFriendlyName() + " has shared a playlist with you", "shareplaylist", model); } @Override public void sendPlaylistNotification(MidasUser updateUser, MidasUser notifyUser, Playlist p) throws IOException { Map model = newModel(notifyUser.getFriendlyName(), notifyUser.getEmail()); model.put("updateUser", updateUser); model.put("playlist", p); model.put("playlistUrl", playlistUrl(p)); String subject = updateUser.getFriendlyName() + " updated their playlist '" + p.getTitle() + "'"; sendMail(updateUser.getEmail(), updateUser.getFriendlyName(), notifyUser.getEmail(), notifyUser.getFriendlyName(), subject, "playlist-notif", model); } @Override public void sendLovesNotification(MidasUser updateUser, MidasUser notifyUser, List<String> artists) throws IOException { Map model = newModel(notifyUser.getFriendlyName(), notifyUser.getEmail()); model.put("updateUser", updateUser); model.put("artists", artists); model.put("lovesUrl", lovesUrl(updateUser.getUserId())); String subject = updateUser.getFriendlyName() + " loves " + TextUtil.numItems(artists, "new artist"); sendMail(updateUser.getEmail(), updateUser.getFriendlyName(), notifyUser.getEmail(), notifyUser.getFriendlyName(), subject, "loves-notif", model); } private String playlistUrl(Playlist p) { return appConfig.getInitParam("shortUrlBase") + "p/" + Long.toHexString(p.getPlaylistId()); } private String lovesUrl(long uid) { return appConfig.getInitParam("shortUrlBase") + "sp/" + uid + "/loves"; } @Override public void sendLibraryNotification(MidasUser updateUser, MidasUser notifyUser, int numTrax) throws IOException { Map model = newModel(notifyUser.getFriendlyName(), notifyUser.getEmail()); model.put("updateUser", updateUser); model.put("numTrax", numTrax); sendMail(updateUser.getEmail(), updateUser.getFriendlyName(), notifyUser.getEmail(), notifyUser.getFriendlyName(), updateUser.getFriendlyName() + " added to their music library", "library-notif", model); } @Override public void sendCommentNotificationForPlaylist(MidasUser owner, MidasUser commentUser, MidasPlaylist p) throws IOException { if (owner.getUserId() == commentUser.getUserId()) return; sendCommentNotification(owner, commentUser, "in your playlist '" + p.getTitle() + "'"); } @Override public void sendCommentNotificationForLibrary(MidasUser libUser, MidasUser commentUser) throws IOException { if (libUser.getUserId() == commentUser.getUserId()) return; sendCommentNotification(libUser, commentUser, "in your music library"); } private void sendCommentNotification(MidasUser updateUser, MidasUser commentUser, String whereIsComment) throws IOException { Map model = newModel(updateUser.getFriendlyName(), updateUser.getEmail()); model.put("commentUser", commentUser); model.put("whereIsComment", whereIsComment); sendMail(commentUser.getEmail(), commentUser.getFriendlyName(), updateUser.getEmail(), updateUser.getFriendlyName(), commentUser.getFriendlyName() + " commented " + whereIsComment, "comment", model); } @Override public void sendReplyNotificationForPlaylist(MidasUser origUser, MidasUser replyUser, MidasPlaylist p) throws IOException { if (origUser.getUserId() == replyUser.getUserId()) return; sendCommentReplyNotification(origUser, replyUser, "in the playlist '" + p.getTitle() + "'"); } @Override public void sendReplyNotificationForLibrary(MidasUser origUser, MidasUser replyUser, long libUserId) throws IOException { if (origUser.getUserId() == replyUser.getUserId()) return; String whereIs; if (libUserId == origUser.getUserId()) whereIs = "in your music library"; else if (libUserId == replyUser.getUserId()) whereIs = "in their music library"; else { MidasUser libUser = userDao.getById(libUserId); whereIs = "in " + libUser.getFriendlyName() + "'s library"; } sendCommentReplyNotification(origUser, replyUser, whereIs); } private void sendCommentReplyNotification(MidasUser origUser, MidasUser replyUser, String whereIsComment) throws IOException { Map model = newModel(origUser.getFriendlyName(), origUser.getEmail()); model.put("replyUser", replyUser); model.put("whereIsComment", whereIsComment); sendMail(replyUser.getEmail(), replyUser.getFriendlyName(), origUser.getEmail(), origUser.getFriendlyName(), replyUser.getFriendlyName() + " replied to your robonobo comment", "commentreply", model); } @Override public void sendCombinedNotification(MidasUser notifyUser, Map<MidasUser, Integer> libTraxAdded, Map<Long, List<Playlist>> playlists, Map<Long, List<String>> loveArtists) throws IOException { List<Map<String, Object>> updateUsers = new ArrayList<Map<String, Object>>(); boolean havePlaylistsOrLoves = false; for (MidasUser updateUser : libTraxAdded.keySet()) { Map<String, Object> userMap = new HashMap<String, Object>(); userMap.put("friendlyName", updateUser.getFriendlyName()); userMap.put("email", updateUser.getEmail()); userMap.put("numLibTrax", libTraxAdded.get(updateUser)); List<Playlist> pObjList = playlists.get(updateUser.getUserId()); List<Map<String, String>> pl = new ArrayList<Map<String, String>>(); for (Playlist p : pObjList) { havePlaylistsOrLoves = true; Map<String, String> pm = new HashMap<String, String>(); String title = p.getTitle(); pm.put("title", title); pm.put("url", playlistUrl(p)); pl.add(pm); } userMap.put("playlists", pl); List<String> ll = loveArtists.get(updateUser.getUserId()); userMap.put("loves", ll); if (ll.size() > 0) { havePlaylistsOrLoves = true; userMap.put("lovesUrl", lovesUrl(updateUser.getUserId())); } updateUsers.add(userMap); } Map model = newModel(notifyUser.getFriendlyName(), notifyUser.getEmail()); model.put("users", updateUsers); model.put("havePlaylistsOrLoves", havePlaylistsOrLoves); sendMail(null, null, notifyUser.getEmail(), notifyUser.getFriendlyName(), "Your friends have added music to robonobo", "combined-notif", model); } @Override public void sendFriendConfirmation(MidasUser userSentFriendReq, MidasUser userConfirmedFriendReq) throws IOException { Map model = newModel(userSentFriendReq.getFriendlyName(), userSentFriendReq.getEmail()); model.put("fromUser", userConfirmedFriendReq); sendMail(userConfirmedFriendReq.getEmail(), userConfirmedFriendReq.getFriendlyName(), userSentFriendReq.getEmail(), userSentFriendReq.getFriendlyName(), userConfirmedFriendReq.getFriendlyName() + " confirmed your robonobo friend request", "friendconfirm", model); } @Override public void sendTopUpRequest(MidasUser requestor) throws IOException { Map model = new HashMap(); model.put("user", requestor); sendMail(null, null, "info@robonobo.com", "rbnb topups", "TopUp Request", "topup", model); } @Override public void sendNewUserNotification(MidasUser user) throws IOException { Map model = new HashMap(); model.put("user", user); sendMail(null, null, "info@robonobo.com", "rbnb admin", "New user: "+user.getEmail(), "newusernotif", model); } private void sendMail(String replyToAddr, String replyToName, String toAddr, String toName, String subject, String templateBase, Map model) throws IOException { if (smtpHost == null) { log.info("Not sending mail to " + toAddr + " with subject '" + subject + "' - no SMTP server configured"); return; } try { Template htmlTemplate = freemarkerCfg.getTemplate(templateBase + "-html.ftl"); StringWriter htmlWri = new StringWriter(); htmlTemplate.process(model, htmlWri); String htmlMsg = htmlWri.toString(); Template textTemplate = freemarkerCfg.getTemplate(templateBase + "-text.ftl"); StringWriter textWri = new StringWriter(); textTemplate.process(model, textWri); String textMsg = textWri.toString(); executor.execute(new MailRunner(replyToAddr, replyToName, toAddr, toName, subject, htmlMsg, textMsg)); } catch (TemplateException e) { throw new IOException(e); } } public static void main(String[] args) throws Exception { Random rand = new Random(); boolean havePlaylistsOrLoves = false; List<Map<String, Object>> userList = new ArrayList<Map<String, Object>>(); for (String userName : Arrays.asList("Foo", "Bar")) { Map<String, Object> userMap = new HashMap<String, Object>(); userMap.put("friendlyName", userName); userMap.put("email", userName.toLowerCase() + "@robonobo.com"); // userMap.put("numLibTrax", 0); userMap.put("numLibTrax", (1 + rand.nextInt(10))); List<Map<String, String>> pl = new ArrayList<Map<String, String>>(); for (String pTitle : Arrays.asList("Testy1", "Testy2", "Testy3")) { havePlaylistsOrLoves = true; Map<String, String> pm = new HashMap<String, String>(); pm.put("title", pTitle); pm.put("url", "http://rbnb.co/foo/" + pTitle); pl.add(pm); } userMap.put("playlists", pl); List<String> ll = Arrays.asList("Thingie", "Thang", "Thong", "Flarble"); // List<String> ll = Arrays.asList(); userMap.put("loves", ll); if (ll.size() > 0) { havePlaylistsOrLoves = true; userMap.put("lovesUrl", "http://rbnb.co/some/loves"); } userList.add(userMap); } Map model = new HashMap(); model.put("toName", "Notify User"); model.put("toEmail", "heydude@robonobo.com"); model.put("rbnbUrl", "http://robonobo.com/"); model.put("users", userList); model.put("havePlaylistsOrLoves", havePlaylistsOrLoves); testTemplate(model, "combined-notif-text.ftl"); } private static void testTemplate(Map model, String templateName) throws TemplateException, IOException { Configuration cfg = new Configuration(); cfg.setTemplateLoader(new FileTemplateLoader(new File("/Users/macavity/src/git/robonobo/midas-webapp/WebContent/WEB-INF/freemarker"))); cfg.setObjectWrapper(new DefaultObjectWrapper()); Template t = cfg.getTemplate(templateName); PrintWriter wri = new PrintWriter(System.out); t.process(model, wri); wri.close(); } class MailRunner implements Runnable { String replyToAddr; String replyToName; String toAddr; String toName; String subject; String htmlMsg; String textMsg; public MailRunner(String replyToAddr, String replyToName, String toAddr, String toName, String subject, String htmlMsg, String textMsg) { this.replyToAddr = replyToAddr; this.replyToName = replyToName; this.toAddr = toAddr; this.toName = toName; this.subject = subject; this.htmlMsg = htmlMsg; this.textMsg = textMsg; } public void run() { try { HtmlEmail mail = new HtmlEmail(); mail.setHostName(smtpHost); mail.setSmtpPort(smtpPort); mail.setTLS(useTls); mail.setAuthentication(smtpUser, smtpPwd); mail.setFrom(appConfig.getInitParam("fromEmail"), appConfig.getInitParam("fromName")); if (replyToAddr != null) mail.addReplyTo(replyToAddr, replyToName); mail.setSubject(subject); if (toName == null) mail.addTo(toAddr); else mail.addTo(toAddr, toName); mail.setHtmlMsg(htmlMsg); mail.setTextMsg(textMsg); mail.send(); log.info("Successfully sent mail to " + toAddr + " with subject '" + subject + "'"); } catch (Exception e) { log.error("Caught exception sending mail to " + toAddr + " with subject '" + subject + "'", e); } } } }