/*
* RHQ Management Platform
* Copyright (C) 2005-2008 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.enterprise.server.core;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import javax.annotation.Resource;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rhq.core.util.stream.StreamUtil;
import org.rhq.enterprise.server.RHQConstants;
import org.rhq.enterprise.server.util.LookupUtil;
/**
* EJB interface to an SMTP email system.
*
* @author John Mazzitelli
*/
@Stateless
public class EmailManagerBean implements EmailManagerLocal {
private static final Log LOG = LogFactory.getLog(EmailManagerBean.class);
/**
* The token string found in the email template file that will be replaced with a resource name.
*/
private static final String TEMPLATE_TOKEN_RESOURCE_NAME = "@@@RESOURCE_NAME@@@";
/**
* The token string found in the email template file that will be replaced with an ascii tree structure containing
* the names of resources up to the corresponding platform
*/
private static final String TEMPLATE_TOKEN_RESOURCE_HIERARCHY = "@@@FULL_RESOURCE_HIERARCHY@@@";
/**
* The token string found in the email template file that will be replaced with an alert name.
*/
private static final String TEMPLATE_TOKEN_ALERT_NAME = "@@@ALERT_NAME@@@";
/**
* The token string found in the email template file that will be replaced with a priority string.
*/
private static final String TEMPLATE_TOKEN_PRIORITY = "@@@PRIORITY@@@";
/**
* The token string found in the email template file that will be replaced with a timestamp.
*/
private static final String TEMPLATE_TOKEN_TIMESTAMP = "@@@TIMESTAMP@@@";
/**
* The token string found in the email template file that will be replaced with a condition set string.
*/
private static final String TEMPLATE_TOKEN_CONDITIONS = "@@@CONDITIONS@@@";
/**
* The token string found in the email template file that will be replaced with a URL to a specific alert.
*/
private static final String TEMPLATE_TOKEN_ALERT_URL = "@@@ALERT_URL@@@";
private static final String TEMPLATE_TOKEN_PRODUCT_NAME = "@@@PRODUCT_NAME@@@";
@Resource(mappedName = "java:jboss/mail/Default")
private Session mailSession;
/**
* Send email to the addressses passed in toAddresses with the passed subject and body. Invalid emails will
* be reported back. This can only catch sender errors up to the first smtp gateway.
* @param toAddresses list of email addresses to send to
* @param messageSubject subject of the email sent
* @param messageBody body of the email to be sent
*
* @return list of email receivers for which initial delivery failed.
*/
public Collection<String> sendEmail(Collection<String> toAddresses, String messageSubject, String messageBody) {
MimeMessage mimeMessage = new MimeMessage(mailSession);
try {
String fromAddress = System.getProperty("rhq.server.email.from-address");
if (fromAddress!=null) {
InternetAddress from = new InternetAddress(fromAddress);
mimeMessage.setFrom(from);
} else {
LOG.warn("No email sender address set in rhq-server.properties [rhq.server.email.from-address]");
}
mimeMessage.setSubject(messageSubject);
mimeMessage.setContent(messageBody, "text/plain");
} catch (MessagingException e) {
e.printStackTrace(); // TODO: Customise this generated block
return toAddresses;
}
Exception error = null;
Collection<String> badAdresses = new ArrayList<String>(toAddresses.size());
// Send to each recipient individually, do not throw exceptions until we try them all
for (String toAddress : toAddresses) {
try {
LOG.debug("Sending email [" + messageSubject + "] to recipient [" + toAddress + "]");
InternetAddress recipient = new InternetAddress(toAddress);
Transport.send(mimeMessage, new InternetAddress[] { recipient });
} catch (Exception e) {
LOG.error("Failed to send email [" + messageSubject + "] to recipient [" + toAddress + "]: "
+ e.getMessage());
badAdresses.add(toAddress);
// Remember the first error - in case its due to a session initialization problem,
// we don't want to lose the first error.
if (error == null) {
error = e;
}
}
}
if (error != null) {
LOG.error("Sending of emails failed for this reason: " + error.getMessage());
}
return badAdresses;
}
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public Map<String, String> getAlertEmailMessage(String resourceHierarchy, String resourceName, String alertName,
String priority, String timestamp, String conditionLogs, String alertUrl) {
InputStream templateStream = this.getClass().getClassLoader().getResourceAsStream("alert-email-template.txt");
String template = new String(StreamUtil.slurp(templateStream));
String productName = LookupUtil.getSystemManager().getProductInfo(LookupUtil.getSubjectManager().getOverlord())
.getFullName();
// the resource hierarchy could have backslash characters from new lines and/or resource names
template = template.replaceAll(TEMPLATE_TOKEN_RESOURCE_HIERARCHY, cleanse(resourceHierarchy,
"?Unknown Resource Hierarchy?"));
// resource names will have backslashes in them when they represent some windows file system service
template = template.replaceAll(TEMPLATE_TOKEN_RESOURCE_NAME, cleanse(resourceName, "?Unknown Resource?"));
// nothing preventing a user from creating an alert definition named "my\cool?definition"
template = template.replaceAll(TEMPLATE_TOKEN_ALERT_NAME, cleanse(alertName, "?Unknown Alert?"));
//if the priority enum for alerts changes in the future, we'll be safe
template = template.replaceAll(TEMPLATE_TOKEN_PRIORITY, cleanse(priority, "Medium"));
// better to be paranoid and on the safe side than risk it just to save one line of code
template = template.replaceAll(TEMPLATE_TOKEN_TIMESTAMP, cleanse(timestamp, new Date().toString()));
// if replacements lookup from the message bundle fails, these will look like "?some.dot.delimited.property?"
template = template.replaceAll(TEMPLATE_TOKEN_CONDITIONS, cleanse(conditionLogs, "?Unknown Condition Logs?"));
// better to be paranoid and on the safe side than risk it just to save one line of code
template = template.replaceAll(TEMPLATE_TOKEN_ALERT_URL, cleanse(alertUrl, "?Unknown URL?"));
template = template.replaceAll(TEMPLATE_TOKEN_PRODUCT_NAME, cleanse(productName, "RHQ"));
String subject = "[" + RHQConstants.PRODUCT_NAME + "] Alert";
if (template.startsWith("Subject:")) {
try {
subject = template.substring("Subject:".length(), template.indexOf('\n'));
} catch (RuntimeException ignore) {
LOG.warn("Bad alert template file - can't determine the subject, using a default");
}
}
Map<String, String> message = new HashMap<String, String>(1);
message.put(subject, template);
return message;
}
/*
* if we don't escape the regex special characters '\' and '$', they will be interpreted differently
* than desired; quoteReplacement was specifically written to help alleviate this common scenario:
*
* http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6523151
*/
private String cleanse(String passedValue, String defaultValue) {
String results = passedValue;
if (results == null) {
results = defaultValue;
}
// cleanse no matter what, because it's possible the defaultValue has invalid characters too
return Matcher.quoteReplacement(results);
}
}