MailNotification.java

/*
 * Copyright (C) 2003-2019 eXo Platform SAS.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see<http://www.gnu.org/licenses/>.
 */
package org.exoplatform.calendar.service.impl;

import java.io.ByteArrayInputStream;
import java.io.OutputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicBoolean;

import org.exoplatform.calendar.service.Attachment;
import org.exoplatform.calendar.service.CalendarEvent;
import org.exoplatform.calendar.service.CalendarService;
import org.exoplatform.calendar.service.CalendarSetting;
import org.exoplatform.calendar.service.Utils;
import org.exoplatform.calendar.util.CalendarUtils;
import org.exoplatform.commons.utils.DateUtils;
import org.exoplatform.container.ExoContainerContext;
import org.exoplatform.container.PortalContainer;
import org.exoplatform.portal.Constants;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.mail.MailService;
import org.exoplatform.services.organization.OrganizationService;
import org.exoplatform.services.organization.User;
import org.exoplatform.services.organization.UserProfile;
import org.exoplatform.services.resources.LocaleContextInfo;
import org.exoplatform.services.resources.ResourceBundleService;
import org.exoplatform.services.security.ConversationState;

public class MailNotification {

  private ResourceBundle        ressourceBundle;

  private MailService           mailService;

  private OrganizationService   organizationService;

  private CalendarService       calendarService;

  public static final   String FIELD_MESSAGE = "messageName" ;
  public static final   String FIELD_EVENT = "eventName";
  public static final   String FIELD_DESCRIPTION = "description";
  public static final   String FIELD_ATTACHMENTS = "attachments";
  public static final   String FIELD_FROM = "from";
  public static final   String FIELD_TO = "to";
  public static final   String FIELD_PLACE = "place";
  public static final   String FIELD_MEETING = "participant";


  
  private static final  AtomicBoolean                 isRBLoaded           = new AtomicBoolean();

  private static final Log      LOG = ExoLogger.getExoLogger(MailNotification.class);
  
  public MailNotification(MailService mailService,
                          OrganizationService organizationService,
                          CalendarService calendarService) {
    this.mailService = mailService;
    this.organizationService = organizationService;
    this.calendarService = calendarService;
  }

  public void sendEmail(CalendarEvent event, String username) throws Exception {
    User invitor = (User) ConversationState.getCurrent().getAttribute("UserProfile");
    if (invitor == null)
      return;
    List<Attachment> atts = event.getAttachment();
    Map<String, String> eXoIdMap = new HashMap<>();   

    StringBuilder toDisplayName = new StringBuilder("");
    StringBuilder sbAddress = new StringBuilder("");
    for (String s : event.getParticipant()) {
      User user = organizationService.getUserHandler().findUserByName(s);
      if (user == null)  {
        continue;
      }
      
      eXoIdMap.put(user.getEmail(), s);
      if (toDisplayName.length() > 0) {
        toDisplayName.append(",");
      }
      toDisplayName.append(user.getDisplayName());
      if (sbAddress.length() > 0)
        sbAddress.append(",");
      sbAddress.append(user.getEmail());
    }

    User user = organizationService.getUserHandler().findUserByName(username);
    byte[] icsFile;
    try (OutputStream out = calendarService.getCalendarImportExports(calendarService.ICALENDAR)
                                           .exportEventCalendar(username,
                                                                event.getCalendarId(),
                                                                event.getCalType(),
                                                                event.getId())) {
      icsFile = out.toString().getBytes("UTF-8");
    }

    String emailList = sbAddress.toString();
    String userId;
    for (String userEmail : emailList.split(CalendarUtils.COMMA)) {
      if (CalendarUtils.isEmpty(userEmail)) continue;

      userId = eXoIdMap.get(userEmail);
      ResourceBundle res = null;

      CalendarSetting calendarSetting = calendarService.getCalendarSetting(userId);
      UserProfile userProfile = organizationService.getUserProfileHandler().findUserProfileByName(userId);
      String lang = userProfile == null ? null : userProfile.getUserInfoMap().get(Constants.USER_LANGUAGE);
      ResourceBundleService ressourceBundleService = ExoContainerContext.getCurrentContainer().getComponentInstanceOfType(ResourceBundleService.class);
      if (lang != null && !lang.isEmpty()) {
        res = ressourceBundleService.getResourceBundle(Utils.RESOURCEBUNDLE_NAME, LocaleContextInfo.getLocale(lang));
      }

      if (res == null) {
        res = getResourceBundle(ressourceBundleService);
      }

      DateFormat df = new SimpleDateFormat(calendarSetting.getDateFormat() + " " + calendarSetting.getTimeFormat());
      df.setTimeZone(DateUtils.getTimeZone(calendarSetting.getTimeZone()));

      org.exoplatform.services.mail.Message message = new org.exoplatform.services.mail.Message();
      message.setSubject(buildMailSubject(event, df, res));
      message.setBody(getBodyMail(buildMailBody(invitor,
                                                event,
                                                toDisplayName.toString(),
                                                df,
                                                CalendarUtils.generateTimeZoneLabel(calendarSetting.getTimeZone()),
                                                res),
                                  eXoIdMap,
                                  userEmail,
                                  invitor,
                                  event,
                                  res));
      message.setTo(userEmail);
      message.setMimeType(Utils.MIMETYPE_TEXTHTML);
      message.setFrom(user.getDisplayName() + "<" + System.getProperty("exo.email.smtp.from") + ">");
      message.setReplyTo(user.getEmail());

      if (icsFile != null) {
        try (ByteArrayInputStream is = new ByteArrayInputStream(icsFile)) {
          org.exoplatform.services.mail.Attachment attachmentCal = new org.exoplatform.services.mail.Attachment();
          attachmentCal.setInputStream(is);
          attachmentCal.setName("icalendar.ics");
          attachmentCal.setMimeType("text/calendar");
          message.addAttachment(attachmentCal);
        }
      }

      if (!atts.isEmpty()) {
        for (Attachment att : atts) {
          org.exoplatform.services.mail.Attachment attachment = new org.exoplatform.services.mail.Attachment();
          attachment.setInputStream(att.getInputStream());
          attachment.setMimeType(att.getMimeType());
          attachment.setName(att.getName());
          message.addAttachment(attachment);
        }
      }
      mailService.sendMessage(message);
    }
  }

  private String getBodyMail(Object sbBody,
                             Map<String, String> eXoIdMap,
                             String userEmail,
                             User invitor,
                             CalendarEvent event,
                             ResourceBundle res) throws Exception {
    StringBuilder body = new StringBuilder(sbBody.toString());
    String eXoId = CalendarUtils.isEmpty(eXoIdMap.get(userEmail)) ? "null":eXoIdMap.get(userEmail);
    body.append("<tr>");
    body.append("<td style=\"padding: 4px;  text-align: right; vertical-align: top; white-space:nowrap;\">");
    body.append(" </td><td> <a href=\""
        + getReplyInvitationLink(org.exoplatform.calendar.service.Utils.ACCEPT, invitor, userEmail, eXoId, event) + "\" >"
        + getLabel(res, "yes") + "</a>" + " - " + "<a href=\""
        + getReplyInvitationLink(org.exoplatform.calendar.service.Utils.NOTSURE, invitor, userEmail, eXoId, event) + "\" >"
        + getLabel(res, "notSure") + "</a>" + " - " + "<a href=\""
        + getReplyInvitationLink(org.exoplatform.calendar.service.Utils.DENY, invitor, userEmail, eXoId, event) + "\" >"
        + getLabel(res, "no") + "</a>");
    body.append("</td></tr>");
    body.append("<tr>");
    body.append("<td style=\"padding: 4px;  text-align: right; vertical-align: top; white-space:nowrap;\">");
    body.append(getLabel(res, "seeMoreDetails") + " </td><td><a href=\""
        + getReplyInvitationLink(org.exoplatform.calendar.service.Utils.ACCEPT_IMPORT, invitor, userEmail, eXoId, event) + "\" >"
        + getLabel(res, "importToExoCalendar") + "</a> " + getLabel(res, "or") + " <a href=\""
        + getReplyInvitationLink(org.exoplatform.calendar.service.Utils.JUMP_TO_CALENDAR, invitor, userEmail, eXoId, event)
        + "\" >" + getLabel(res, "jumpToExoCalendar") + "</a>");
    body.append("</td></tr>");
    body.append("</tbody>");
    body.append("</table>");
    body.append("</div>");
    return body.toString();
  }

  private static String getReplyInvitationLink(int answer,
                                        User invitor,
                                        String invitee,
                                        String eXoId,
                                        CalendarEvent event) throws Exception {
    String portalURL = CalendarUtils.getServerBaseUrl() + "/" + PortalContainer.getCurrentPortalContainerName();
    String restURL = portalURL + "/" + PortalContainer.getCurrentRestContextName();
    String calendarURL = portalURL + "/intranet/calendar";

    if (answer == org.exoplatform.calendar.service.Utils.ACCEPT || answer == org.exoplatform.calendar.service.Utils.DENY
        || answer == org.exoplatform.calendar.service.Utils.NOTSURE) {
      return (restURL + "/cs/calendar" + CalendarUtils.INVITATION_URL + event.getCalendarId() + "/" + event.getCalType() + "/"
          + event.getId() + "/" + invitor.getUserName() + "/" + invitee + "/" + eXoId + "/" + answer);
    }
    if (answer == org.exoplatform.calendar.service.Utils.ACCEPT_IMPORT) {
      return (calendarURL + CalendarUtils.INVITATION_IMPORT_URL + invitor.getUserName() + "/" + event.getId() + "/" + event.getCalType());
    }
    if (answer == org.exoplatform.calendar.service.Utils.JUMP_TO_CALENDAR) {
      return (calendarURL + CalendarUtils.INVITATION_DETAIL_URL + invitor.getUserName() + "/" + event.getId() + "/" + event.getCalType());
    }
    return "";
  }

  private Object buildMailBody(User invitor,
                               CalendarEvent event,
                               String toDisplayName,
                               DateFormat df,
                               String timezone,
                               ResourceBundle res) throws Exception {
    List<Attachment> atts = event.getAttachment();

    StringBuilder sbBody = new StringBuilder();
    sbBody.append("<div style=\"margin: 20px auto; padding: 8px; background: rgb(224, 236, 255) none repeat scroll 0%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial; width: 500px;\">");
    sbBody.append("<table style=\"margin: 0px; padding: 0px; border-collapse: collapse; border-spacing: 0px; width: 100%; line-height: 16px;\">");
    sbBody.append("<tbody>");
    sbBody.append("<tr>");
    sbBody.append("<td style=\"padding: 4px;  text-align: right; vertical-align: top; white-space:nowrap; \">"
        + getLabel(res, "fromWho") + ":</td>");
    sbBody.append("<td style=\"padding: 4px;\"> " + invitor.getDisplayName() + " (" + invitor.getEmail() + ")" + " </td>");
    sbBody.append("</tr>");

    sbBody.append("<tr>");
    sbBody.append("<td style=\"padding: 4px;  text-align: right; vertical-align: top; white-space:nowrap;\">"
        + getLabel(res, FIELD_MESSAGE) + ":</td>");
    sbBody.append("<td style=\"padding: 4px;\">" + event.getMessage() + "</td>");
    sbBody.append("</tr>");

    sbBody.append("<tr>");
    sbBody.append("<td style=\"padding: 4px;  text-align: right; vertical-align: top; white-space:nowrap;\">"
        + getLabel(res, FIELD_EVENT) + ":</td>");
    sbBody.append("<td style=\"padding: 4px;\">" + event.getSummary() + "</td>");
    sbBody.append("</tr>");
    sbBody.append("<tr>");
    sbBody.append("<td style=\"padding: 4px;  text-align: right; vertical-align: top; white-space:nowrap;\">"
        + getLabel(res, FIELD_DESCRIPTION) + ":</td>");
    sbBody.append("<td style=\"padding: 4px;\">"
        + (event.getDescription() != null && event.getDescription().trim().length() > 0 ? event.getDescription() : " ")
        + "</td>");
    sbBody.append("</tr>");
    sbBody.append("<tr>");
    sbBody.append("<td style=\"padding: 4px;  text-align: right; vertical-align: top; white-space:nowrap;\">"
        + getLabel(res, "when") + ":</td>");
    sbBody.append("<td style=\"padding: 4px;\"> <div>" + getLabel(res, FIELD_FROM) + ": "
        + df.format(event.getFromDateTime()) + " " + timezone + "</div>");
    sbBody.append("<div>" + getLabel(res, FIELD_TO) + ": " + df.format(event.getToDateTime()) + " " + timezone
        + "</div></td>");
    sbBody.append("</tr>");
    sbBody.append("<tr>");
    sbBody.append("<td style=\"padding: 4px;  text-align: right; vertical-align: top; white-space:nowrap;\">"
        + getLabel(res, FIELD_PLACE) + ":</td>");
    sbBody.append("<td style=\"padding: 4px;\">"
        + (event.getLocation() != null && event.getLocation().trim().length() > 0 ? event.getLocation() : " ") + "</td>");
    sbBody.append("</tr>");
    sbBody.append("<tr>");
    sbBody.append("<td style=\"padding: 4px;  text-align: right; vertical-align: top; white-space:nowrap;\">"
        + getLabel(res, FIELD_MEETING) + "</td>");
    sbBody.append("<td style=\"padding: 4px;\">" + toDisplayName + "</td>");
    sbBody.append("</tr>");
    if (!atts.isEmpty()) {
      sbBody.append("<tr>");
      sbBody.append("<td style=\"padding: 4px;  text-align: right; vertical-align: top; white-space:nowrap;\">"
          + getLabel(res, FIELD_ATTACHMENTS) + ":</td>");
      StringBuilder sbf = new StringBuilder();
      for (Attachment att : atts) {
        if (sbf.length() > 0)
          sbf.append(",");
        sbf.append(att.getName());
      }
      sbBody.append("<td style=\"padding: 4px;\"> (" + atts.size() + ") " + sbf.toString() + " </td>");
      sbBody.append("</tr>");
    }

    return sbBody.toString();
  }

  private String buildMailSubject(CalendarEvent event, DateFormat df, ResourceBundle res) {
    StringBuilder sbSubject = new StringBuilder("[" + getLabel(res, "invitation") + "] ");
    sbSubject.append(event.getSummary());
    Date fromDateTime = event.getFromDateTime();
    if(fromDateTime != null) {
      sbSubject.append(" ");
      sbSubject.append(df.format(fromDateTime));
    }

    return sbSubject.toString();
  }

  public String getLabel(ResourceBundle res, String label) {
    if(res != null) {
      String resKey = ".label." + label;
      try {
        return res.getString(resKey);
      } catch (MissingResourceException e) {
        return label;
      }
    } else {
      return label;
    }
  }
  
  public ResourceBundle getResourceBundle(ResourceBundleService ressourceBundleService) throws Exception {
    if (!isRBLoaded.get()) {
      synchronized (isRBLoaded) {
        if (!isRBLoaded.get()) {
          try {
            ressourceBundle = ressourceBundleService.getResourceBundle(Utils.RESOURCEBUNDLE_NAME, Locale.getDefault());
          } catch (MissingResourceException e) {
            ressourceBundle = null;
          }
          isRBLoaded.set(true);
        }
      }
    } 
    return ressourceBundle;
  }
}