/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package edu.harvard.iq.dataverse;
import com.sun.mail.smtp.SMTPSendFailedException;
import edu.harvard.iq.dataverse.authorization.groups.Group;
import edu.harvard.iq.dataverse.authorization.groups.GroupServiceBean;
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
import edu.harvard.iq.dataverse.confirmemail.ConfirmEmailServiceBean;
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
import edu.harvard.iq.dataverse.settings.SettingsServiceBean.Key;
import edu.harvard.iq.dataverse.util.BundleUtil;
import edu.harvard.iq.dataverse.util.MailUtil;
import edu.harvard.iq.dataverse.util.SystemConfig;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Properties;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.logging.Logger;
import javax.annotation.Resource;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import org.apache.commons.lang.StringUtils;
/**
*
* original author: roberttreacy
*/
@Stateless
public class MailServiceBean implements java.io.Serializable {
@EJB
UserNotificationServiceBean userNotificationService;
@EJB
DataverseServiceBean dataverseService;
@EJB
DataFileServiceBean dataFileService;
@EJB
DatasetServiceBean datasetService;
@EJB
DatasetVersionServiceBean versionService;
@EJB
SystemConfig systemConfig;
@EJB
SettingsServiceBean settingsService;
@EJB
PermissionServiceBean permissionService;
@EJB
GroupServiceBean groupService;
@EJB
ConfirmEmailServiceBean confirmEmailService;
private static final Logger logger = Logger.getLogger(MailServiceBean.class.getCanonicalName());
private static final String EMAIL_PATTERN = "^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@"
+ "[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";
/**
* Creates a new instance of MailServiceBean
*/
public MailServiceBean() {
}
public void sendMail(String host, String from, String to, String subject, String messageText) {
Properties props = System.getProperties();
props.put("mail.smtp.host", host);
Session session = Session.getDefaultInstance(props, null);
try {
Message msg = new MimeMessage(session);
msg.setFrom(new InternetAddress(from));
msg.setRecipients(Message.RecipientType.TO,
InternetAddress.parse(to, false));
msg.setSubject(subject);
msg.setText(messageText);
Transport.send(msg);
} catch (AddressException ae) {
ae.printStackTrace(System.out);
} catch (MessagingException me) {
me.printStackTrace(System.out);
}
}
@Resource(name = "mail/notifyMailSession")
private Session session;
public boolean sendSystemEmail(String to, String subject, String messageText) {
boolean sent = false;
try {
Message msg = new MimeMessage(session);
InternetAddress systemAddress = getSystemAddress();
if (systemAddress != null) {
msg.setFrom(systemAddress);
msg.setSentDate(new Date());
msg.setRecipients(Message.RecipientType.TO,
InternetAddress.parse(to, false));
msg.setSubject(subject);
msg.setText(messageText + ResourceBundle.getBundle("Bundle").getString("notification.email.closing"));
try {
Transport.send(msg);
sent = true;
} catch (SMTPSendFailedException ssfe) {
logger.warning("Failed to send mail to " + to + " (SMTPSendFailedException)");
}
} else {
logger.fine("Skipping sending mail to " + to + ", because the \"no-reply\" address not set (" + Key.SystemEmail + " setting).");
}
} catch (AddressException ae) {
logger.warning("Failed to send mail to " + to);
ae.printStackTrace(System.out);
} catch (MessagingException me) {
logger.warning("Failed to send mail to " + to);
me.printStackTrace(System.out);
}
return sent;
}
private InternetAddress getSystemAddress() {
String systemEmail = settingsService.getValueForKey(Key.SystemEmail);
return MailUtil.parseSystemAddress(systemEmail);
}
//@Resource(name="mail/notifyMailSession")
public void sendMail(String from, String to, String subject, String messageText) {
sendMail(from, to, subject, messageText, new HashMap());
}
public void sendMail(String from, String to, String subject, String messageText, Map extraHeaders) {
try {
Message msg = new MimeMessage(session);
if (from.matches(EMAIL_PATTERN)) {
msg.setFrom(new InternetAddress(from));
} else {
// set fake from address; instead, add it as part of the message
//msg.setFrom(new InternetAddress("invalid.email.address@mailinator.com"));
msg.setFrom(getSystemAddress());
messageText = "From: " + from + "\n\n" + messageText;
}
msg.setSentDate(new Date());
msg.setRecipients(Message.RecipientType.TO,
InternetAddress.parse(to, false));
msg.setSubject(subject);
msg.setText(messageText);
if (extraHeaders != null) {
for (Object key : extraHeaders.keySet()) {
String headerName = key.toString();
String headerValue = extraHeaders.get(key).toString();
msg.addHeader(headerName, headerValue);
}
}
Transport.send(msg);
} catch (AddressException ae) {
ae.printStackTrace(System.out);
} catch (MessagingException me) {
me.printStackTrace(System.out);
}
}
public Boolean sendNotificationEmail(UserNotification notification){
boolean retval = false;
String emailAddress = getUserEmailAddress(notification);
if (emailAddress != null){
Object objectOfNotification = getObjectOfNotification(notification);
if (objectOfNotification != null){
String messageText = getMessageTextBasedOnNotification(notification, objectOfNotification);
String subjectText = getSubjectTextBasedOnNotification(notification);
if (!(messageText.isEmpty() || subjectText.isEmpty())){
retval = sendSystemEmail(emailAddress, subjectText, messageText);
} else {
logger.warning("Skipping " + notification.getType() + " notification, because couldn't get valid message");
}
} else {
logger.warning("Skipping " + notification.getType() + " notification, because no valid Object was found");
}
} else {
logger.warning("Skipping " + notification.getType() + " notification, because email address is null");
}
return retval;
}
private String getSubjectTextBasedOnNotification(UserNotification userNotification) {
switch (userNotification.getType()) {
case ASSIGNROLE:
return ResourceBundle.getBundle("Bundle").getString("notification.email.assign.role.subject");
case REVOKEROLE:
return ResourceBundle.getBundle("Bundle").getString("notification.email.revoke.role.subject");
case CREATEDV:
return ResourceBundle.getBundle("Bundle").getString("notification.email.create.dataverse.subject");
case REQUESTFILEACCESS:
return ResourceBundle.getBundle("Bundle").getString("notification.email.request.file.access.subject");
case GRANTFILEACCESS:
return ResourceBundle.getBundle("Bundle").getString("notification.email.grant.file.access.subject");
case REJECTFILEACCESS:
return ResourceBundle.getBundle("Bundle").getString("notification.email.rejected.file.access.subject");
case MAPLAYERUPDATED:
return ResourceBundle.getBundle("Bundle").getString("notification.email.update.maplayer");
case CREATEDS:
return ResourceBundle.getBundle("Bundle").getString("notification.email.create.dataset.subject");
case SUBMITTEDDS:
return ResourceBundle.getBundle("Bundle").getString("notification.email.submit.dataset.subject");
case PUBLISHEDDS:
return ResourceBundle.getBundle("Bundle").getString("notification.email.publish.dataset.subject");
case RETURNEDDS:
return ResourceBundle.getBundle("Bundle").getString("notification.email.returned.dataset.subject");
case CREATEACC:
return ResourceBundle.getBundle("Bundle").getString("notification.email.create.account.subject");
}
return "";
}
private String getDatasetManageFileAccessLink(DataFile datafile){
return systemConfig.getDataverseSiteUrl() + "/permissions-manage-files.xhtml?id=" + datafile.getOwner().getId();
}
private String getDatasetLink(Dataset dataset){
return systemConfig.getDataverseSiteUrl() + "/dataset.xhtml?persistentId=" + dataset.getGlobalId();
}
private String getDatasetDraftLink(Dataset dataset){
return systemConfig.getDataverseSiteUrl() + "/dataset.xhtml?persistentId=" + dataset.getGlobalId() + "&version=DRAFT" + "&faces-redirect=true";
}
private String getDataverseLink(Dataverse dataverse){
return systemConfig.getDataverseSiteUrl() + "/dataverse/" + dataverse.getAlias();
}
/**
* Returns a '/'-separated string of roles that are effective for {@code au}
* over {@code dvObj}. Traverses the containment hierarchy of the {@code d}.
* Takes into consideration all groups that {@code au} is part of.
* @param au The authenticated user whose role assignments we look for.
* @param dvObj The Dataverse object over which the roles are assigned
* @return A set of all the role assignments for {@code ra} over {@code d}.
*/
private String getRoleStringFromUser(AuthenticatedUser au, DvObject dvObj) {
// Find user's role(s) for given dataverse/dataset
Set<RoleAssignment> roles = permissionService.assignmentsFor(au, dvObj);
List<String> roleNames = new ArrayList();
// Include roles derived from a user's groups
Set<Group> groupsUserBelongsTo = groupService.groupsFor(au, dvObj);
for (Group g : groupsUserBelongsTo) {
roles.addAll(permissionService.assignmentsFor(g, dvObj));
}
for (RoleAssignment ra : roles) {
roleNames.add(ra.getRole().getName());
}
return StringUtils.join(roleNames, "/");
}
/**
* Returns the URL to a given {@code DvObject} {@code d}. If {@code d} is a
* {@code DataFile}, return a link to its {@code DataSet}.
* @param d The Dataverse object to get a link for.
* @return A string with a URL to the given Dataverse object.
*/
private String getDvObjectLink(DvObject d) {
if (d instanceof Dataverse) {
return getDataverseLink((Dataverse) d);
} else if (d instanceof Dataset) {
return getDatasetLink((Dataset) d);
} else if (d instanceof DataFile) {
return getDatasetLink(((DataFile) d).getOwner());
}
return "";
}
/**
* Returns string representation of the type of {@code DvObject} {@code d}.
* @param d The Dataverse object to get the string for
* @return A string that represents the type of a given Dataverse object.
*/
private String getDvObjectTypeString(DvObject d) {
if (d instanceof Dataverse) {
return "dataverse";
} else if (d instanceof Dataset) {
return "dataset";
} else if (d instanceof DataFile) {
return "data file";
}
return "";
}
private String getMessageTextBasedOnNotification(UserNotification userNotification, Object targetObject){
String messageText = ResourceBundle.getBundle("Bundle").getString("notification.email.greeting");
DatasetVersion version = null;
Dataset dataset = null;
DvObject dvObj = null;
String dvObjURL = null;
String dvObjTypeStr = null;
String pattern ="";
switch (userNotification.getType()) {
case ASSIGNROLE:
AuthenticatedUser au = userNotification.getUser();
dvObj = (DvObject) targetObject;
String joinedRoleNames = getRoleStringFromUser(au, dvObj);
dvObjURL = getDvObjectLink(dvObj);
dvObjTypeStr = getDvObjectTypeString(dvObj);
pattern = ResourceBundle.getBundle("Bundle").getString("notification.email.assignRole");
String[] paramArrayAssignRole = {joinedRoleNames, dvObjTypeStr, dvObj.getDisplayName(), dvObjURL};
messageText += MessageFormat.format(pattern, paramArrayAssignRole);
if (joinedRoleNames.contains("File Downloader")){
if (dvObjTypeStr.equals("dataset")){
pattern = ResourceBundle.getBundle("Bundle").getString("notification.access.granted.fileDownloader.additionalDataset");
String[] paramArrayAssignRoleDS = {" "};
messageText += MessageFormat.format(pattern, paramArrayAssignRoleDS);
}
if (dvObjTypeStr.equals("dataverse")){
pattern = ResourceBundle.getBundle("Bundle").getString("notification.access.granted.fileDownloader.additionalDataverse");
String[] paramArrayAssignRoleDV = {" "};
messageText += MessageFormat.format(pattern, paramArrayAssignRoleDV);
}
}
return messageText;
case REVOKEROLE:
dvObj = (DvObject) targetObject;
dvObjURL = getDvObjectLink(dvObj);
dvObjTypeStr = getDvObjectTypeString(dvObj);
pattern = ResourceBundle.getBundle("Bundle").getString("notification.email.revokeRole");
String[] paramArrayRevokeRole = {dvObjTypeStr, dvObj.getDisplayName(), dvObjURL};
messageText += MessageFormat.format(pattern, paramArrayRevokeRole);
return messageText;
case CREATEDV:
Dataverse dataverse = (Dataverse) targetObject;
Dataverse parentDataverse = dataverse.getOwner();
// initialize to empty string in the rare case that there is no parent dataverse (i.e. root dataverse just created)
String parentDataverseDisplayName = "";
String parentDataverseUrl = "";
if (parentDataverse != null) {
parentDataverseDisplayName = parentDataverse.getDisplayName();
parentDataverseUrl = getDataverseLink(parentDataverse);
}
String dataverseCreatedMessage = BundleUtil.getStringFromBundle("notification.email.createDataverse", Arrays.asList(
dataverse.getDisplayName(),
getDataverseLink(dataverse),
parentDataverseDisplayName,
parentDataverseUrl,
systemConfig.getGuidesBaseUrl(),
systemConfig.getVersion()));
logger.fine(dataverseCreatedMessage);
return messageText += dataverseCreatedMessage;
case REQUESTFILEACCESS:
DataFile datafile = (DataFile) targetObject;
pattern = ResourceBundle.getBundle("Bundle").getString("notification.email.requestFileAccess");
String[] paramArrayRequestFileAccess = {datafile.getOwner().getDisplayName(), getDatasetManageFileAccessLink(datafile)};
messageText += MessageFormat.format(pattern, paramArrayRequestFileAccess);
return messageText;
case GRANTFILEACCESS:
dataset = (Dataset) targetObject;
pattern = ResourceBundle.getBundle("Bundle").getString("notification.email.grantFileAccess");
String[] paramArrayGrantFileAccess = {dataset.getDisplayName(), getDatasetLink(dataset)};
messageText += MessageFormat.format(pattern, paramArrayGrantFileAccess);
return messageText;
case REJECTFILEACCESS:
dataset = (Dataset) targetObject;
pattern = ResourceBundle.getBundle("Bundle").getString("notification.email.rejectFileAccess");
String[] paramArrayRejectFileAccess = {dataset.getDisplayName(), getDatasetLink(dataset)};
messageText += MessageFormat.format(pattern, paramArrayRejectFileAccess);
return messageText;
case CREATEDS:
version = (DatasetVersion) targetObject;
String datasetCreatedMessage = BundleUtil.getStringFromBundle("notification.email.createDataset", Arrays.asList(
version.getDataset().getDisplayName(),
getDatasetLink(version.getDataset()),
version.getDataset().getOwner().getDisplayName(),
getDataverseLink(version.getDataset().getOwner()),
systemConfig.getGuidesBaseUrl(),
systemConfig.getVersion()
));
logger.fine(datasetCreatedMessage);
return messageText += datasetCreatedMessage;
case MAPLAYERUPDATED:
version = (DatasetVersion) targetObject;
pattern = ResourceBundle.getBundle("Bundle").getString("notification.email.worldMap.added");
String[] paramArrayMapLayer = {version.getDataset().getDisplayName(), getDatasetLink(version.getDataset())};
messageText += MessageFormat.format(pattern, paramArrayMapLayer);
return messageText;
case SUBMITTEDDS:
version = (DatasetVersion) targetObject;
pattern = ResourceBundle.getBundle("Bundle").getString("notification.email.wasSubmittedForReview");
String[] paramArraySubmittedDataset = {version.getDataset().getDisplayName(), getDatasetDraftLink(version.getDataset()),
version.getDataset().getOwner().getDisplayName(), getDataverseLink(version.getDataset().getOwner())};
messageText += MessageFormat.format(pattern, paramArraySubmittedDataset);
return messageText;
case PUBLISHEDDS:
version = (DatasetVersion) targetObject;
pattern = ResourceBundle.getBundle("Bundle").getString("notification.email.wasPublished");
String[] paramArrayPublishedDataset = {version.getDataset().getDisplayName(), getDatasetLink(version.getDataset()),
version.getDataset().getOwner().getDisplayName(), getDataverseLink(version.getDataset().getOwner())};
messageText += MessageFormat.format(pattern, paramArrayPublishedDataset);
return messageText;
case RETURNEDDS:
version = (DatasetVersion) targetObject;
pattern = ResourceBundle.getBundle("Bundle").getString("notification.email.wasReturnedByReviewer");
String[] paramArrayReturnedDataset = {version.getDataset().getDisplayName(), getDatasetDraftLink(version.getDataset()),
version.getDataset().getOwner().getDisplayName(), getDataverseLink(version.getDataset().getOwner())};
messageText += MessageFormat.format(pattern, paramArrayReturnedDataset);
return messageText;
case CREATEACC:
String accountCreatedMessage = BundleUtil.getStringFromBundle("notification.email.welcome", Arrays.asList(
systemConfig.getGuidesBaseUrl(),
systemConfig.getVersion()
));
String optionalConfirmEmailAddon = confirmEmailService.optionalConfirmEmailAddonMsg(userNotification.getUser());
accountCreatedMessage += optionalConfirmEmailAddon;
logger.info("accountCreatedMessage: " + accountCreatedMessage);
return messageText += accountCreatedMessage;
}
return "";
}
private Object getObjectOfNotification (UserNotification userNotification){
switch (userNotification.getType()) {
case ASSIGNROLE:
case REVOKEROLE:
// Can either be a dataverse or dataset, so search both
Dataverse dataverse = dataverseService.find(userNotification.getObjectId());
if (dataverse != null) return dataverse;
Dataset dataset = datasetService.find(userNotification.getObjectId());
return dataset;
case CREATEDV:
return dataverseService.find(userNotification.getObjectId());
case REQUESTFILEACCESS:
return dataFileService.find(userNotification.getObjectId());
case GRANTFILEACCESS:
case REJECTFILEACCESS:
return datasetService.find(userNotification.getObjectId());
case MAPLAYERUPDATED:
case CREATEDS:
case SUBMITTEDDS:
case PUBLISHEDDS:
case RETURNEDDS:
return versionService.find(userNotification.getObjectId());
case CREATEACC:
return userNotification.getUser();
}
return null;
}
private String getUserEmailAddress(UserNotification notification) {
if (notification != null) {
if (notification.getUser() != null) {
if (notification.getUser().getDisplayInfo() != null) {
if (notification.getUser().getDisplayInfo().getEmailAddress() != null) {
logger.fine("Email address: "+notification.getUser().getDisplayInfo().getEmailAddress());
return notification.getUser().getDisplayInfo().getEmailAddress();
}
}
}
}
logger.fine("no email address");
return null;
}
}