/*
* NOTE: This copyright does *not* cover user programs that use Hyperic
* program services by normal system calls through the application
* program interfaces provided as part of the Hyperic Plug-in Development
* Kit or the Hyperic Client Development Kit - this is merely considered
* normal use of the program, and does *not* fall under the heading of
* "derived work".
*
* Copyright (C) [2004-2010], VMware, Inc.
* This file is part of Hyperic.
*
* Hyperic is free software; you can redistribute it and/or modify
* it under the terms version 2 of the GNU General Public License as
* published by the Free Software Foundation. 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA.
*/
package org.hyperic.hq.bizapp.server.session;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Hashtable;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import javax.annotation.PostConstruct;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperic.hq.appdef.shared.AppdefEntityConstants;
import org.hyperic.hq.appdef.shared.AppdefEntityID;
import org.hyperic.hq.appdef.shared.PlatformManager;
import org.hyperic.hq.appdef.shared.PlatformNotFoundException;
import org.hyperic.hq.authz.shared.ResourceManager;
import org.hyperic.hq.bizapp.server.action.email.EmailFilterJob;
import org.hyperic.hq.bizapp.server.action.email.EmailRecipient;
import org.hyperic.hq.bizapp.shared.EmailManager;
import org.hyperic.hq.common.shared.HQConstants;
import org.hyperic.hq.common.shared.ServerConfigManager;
import org.hyperic.hq.context.Bootstrap;
import org.hyperic.hq.events.EventConstants;
import org.hyperic.hq.measurement.MeasurementConstants;
import org.hyperic.hq.stats.ConcurrentStatsCollector;
import org.hyperic.util.ConfigPropertyException;
import org.hyperic.util.collection.IntHashMap;
import org.hyperic.util.timer.StopWatch;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.MailException;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
/**
*/
@Service
public class EmailManagerImpl implements EmailManager {
private JavaMailSender mailSender;
private ServerConfigManager serverConfigManager;
private PlatformManager platformManager;
private ResourceManager resourceManager;
private Session mailSession;
private ConcurrentStatsCollector concurrentStatsCollector;
private int mailSmtpConnectiontimeout;
private int mailSmtpTimeout;
private String mailSmtpHost;
final Log log = LogFactory.getLog(EmailManagerImpl.class);
public static final String JOB_GROUP = "EmailFilterGroup";
private static final IntHashMap _alertBuffer = new IntHashMap();
public static final Object SCHEDULER_LOCK = new Object();
@Autowired
public EmailManagerImpl(JavaMailSender mailSender, ServerConfigManager serverConfigManager,
PlatformManager platformManager, ResourceManager resourceManager,
Session mailSession, ConcurrentStatsCollector concurrentStatsCollector) {
this.mailSender = mailSender;
this.mailSession = mailSession;
this.serverConfigManager = serverConfigManager;
this.platformManager = platformManager;
this.resourceManager = resourceManager;
this.concurrentStatsCollector = concurrentStatsCollector;
mailSmtpConnectiontimeout = Integer.parseInt(mailSession.getProperties().getProperty(HQConstants.MAIL_SMTP_CONNECTIONTIMEOUT));
mailSmtpTimeout = Integer.parseInt(mailSession.getProperties().getProperty(HQConstants.MAIL_SMTP_TIMEOUT));
mailSmtpHost = mailSession.getProperties().getProperty(HQConstants.MAIL_SMTP_HOST);
}
@PostConstruct
public void initStats() {
concurrentStatsCollector.register(ConcurrentStatsCollector.SEND_ALERT_TIME);
}
public void sendEmail(EmailRecipient[] addresses, String subject, String[] body, String[] htmlBody, Integer priority) {
MimeMessage mimeMessage = mailSender.createMimeMessage();
final StopWatch watch = new StopWatch();
try {
InternetAddress from = getFromAddress();
if (from == null) {
mimeMessage.setFrom();
} else {
mimeMessage.setFrom(from);
}
// HHQ-5708
// remove any possible new line from the subject
// the subject can be render form 'subject.gsp' file
mimeMessage.setSubject(subject.replace("\r", "").replace("\n", ""));
// If priority not null, set it in body
if (priority != null) {
switch (priority.intValue()) {
case EventConstants.PRIORITY_HIGH:
mimeMessage.addHeader("X-Priority", "1");
break;
case EventConstants.PRIORITY_MEDIUM:
mimeMessage.addHeader("X-Priority", "2");
break;
default:
break;
}
}
// Send to each recipient individually (for D.B. SMS)
for (int i = 0; i < addresses.length; i++) {
mimeMessage.setRecipient(Message.RecipientType.TO, addresses[i].getAddress());
if (addresses[i].useHtml()) {
mimeMessage.setContent(htmlBody[i], "text/html; charset=UTF-8");
if (log.isDebugEnabled()) {
log.debug("Sending HTML Alert notification: " + subject + " to " +
addresses[i].getAddress().getAddress() +
"\n" + htmlBody[i]);
}
} else {
if (log.isDebugEnabled()) {
log.debug("Sending Alert notification: " + subject + " to " +
addresses[i].getAddress().getAddress() +
"\n" + body[i]);
}
mimeMessage.setContent(body[i], "text/plain; charset=UTF-8");
}
mailSender.send(mimeMessage);
}
} catch (MessagingException e) {
log.error("MessagingException in sending email: [" + subject + "]\nmailServer = [" + mailSession.getProperties() + "]", e);
} catch (MailException me) {
log.error("MailException in sending email: [" + subject + "]\nmailServer = [" + mailSession.getProperties() + "]", me);
} catch (Exception ex) {
log.error("Error in sending email: [" + subject + "]\nmailServer = [" + mailSession.getProperties() + "]", ex);
} finally {
if (log.isDebugEnabled()){
log.debug("Sending email using mailServer=" + mailSession.getProperties() +
" took " + watch.getElapsed() + " ms.");
}
if (watch.getElapsed() >= mailSmtpConnectiontimeout
|| (watch.getElapsed() >= mailSmtpTimeout)) {
log.warn("Sending email using mailServer=" + mailSmtpHost +
" took " + watch.getElapsed() + " ms. Please check with your mail administrator.");
}
}
}
private InternetAddress getFromAddress() {
try {
Properties props = serverConfigManager.getConfig();
String from = props.getProperty(HQConstants.EmailSender);
if (from != null) {
return new InternetAddress(from);
}
} catch (ConfigPropertyException e) {
log.error("ConfigPropertyException fetch FROM address", e);
} catch (AddressException e) {
log.error("Bad FROM address", e);
}
return null;
}
@SuppressWarnings("unchecked")
public void sendFiltered(Integer platId) {
Hashtable<EmailRecipient, FilterBuffer> cache;
synchronized (_alertBuffer) {
if (!_alertBuffer.containsKey(platId)) {
return;
}
cache = (Hashtable<EmailRecipient, FilterBuffer>) _alertBuffer.remove(platId);
if (cache == null || cache.size() == 0) {
return;
}
// Insert key again so that we continue filtering
_alertBuffer.put(platId, null);
}
AppdefEntityID platEntId = AppdefEntityID.newPlatformID(platId);
String platName = resourceManager.getAppdefEntityName(platEntId);
// The cache is organized by addresses
for (Entry<EmailRecipient, FilterBuffer> ent : cache.entrySet()) {
EmailRecipient addr = (EmailRecipient) ent.getKey();
FilterBuffer msg = (FilterBuffer) ent.getValue();
if (msg.getNumEnts() == 1 && addr.useHtml()) {
sendEmail(new EmailRecipient[] { addr }, "[HQ] Filtered Notifications for " + platName,
new String[] { "" }, new String[] { msg.getHtml() }, null);
} else {
addr.setHtml(false);
sendEmail(new EmailRecipient[] { addr }, "[HQ] Filtered Notifications for " + platName,
new String[] { msg.getText() }, new String[] { "" }, null);
}
}
}
public void sendAlert(AppdefEntityID appEnt, EmailRecipient[] addresses, String subject, String[] body,
String[] htmlBody, int priority, boolean filter) {
final StopWatch watch = new StopWatch();
try {
_sendAlert(appEnt, addresses, subject, body, htmlBody, priority, filter);
} finally {
concurrentStatsCollector.addStat(watch.getElapsed(), ConcurrentStatsCollector.SEND_ALERT_TIME);
}
}
private void _sendAlert(AppdefEntityID appEnt, EmailRecipient[] addresses, String subject, String[] body,
String[] htmlBody, int priority, boolean filter) {
if (appEnt == null) {
// Go ahead and just send the alert
sendEmail(addresses, subject, body, htmlBody, new Integer(priority));
return;
}
// See if alert needs to be filtered
if (filter) {
try {
// Now let's look up the platform ID
Integer platId;
switch (appEnt.getType()) {
case AppdefEntityConstants.APPDEF_TYPE_PLATFORM:
platId = appEnt.getId();
break;
case AppdefEntityConstants.APPDEF_TYPE_SERVER:
platId = platformManager.getPlatformIdByServer(appEnt.getId());
break;
case AppdefEntityConstants.APPDEF_TYPE_SERVICE:
platId = platformManager.getPlatformIdByService(appEnt.getId());
break;
default:
platId = null;
break;
}
filter = false;
// Let's see if we are adding or sending
if (platId != null) {
synchronized (_alertBuffer) {
if (_alertBuffer.containsKey(platId.intValue())) {
// Queue it up
@SuppressWarnings({ "unchecked", "rawtypes" })
Map<EmailRecipient, FilterBuffer> cache = (Map) _alertBuffer.get(platId.intValue());
if (cache == null) {
// Make sure we check again in 5 minutes
cache = new Hashtable<EmailRecipient, FilterBuffer>();
_alertBuffer.put(platId.intValue(), cache);
}
for (int i = 0; i < addresses.length; i++) {
FilterBuffer msg;
if (cache.containsKey(addresses[i])) {
// Create new buffer with previous body
msg = (FilterBuffer) cache.get(addresses[i]);
msg.append("\n", "\n");
} else {
msg = new FilterBuffer();
}
msg.incrementEntries();
msg.append(body[i], htmlBody[i]);
cache.put(addresses[i], msg);
}
filter = true;
} else {
// Add a new queue
_alertBuffer.put(platId.intValue(), new Hashtable<EmailRecipient, FilterBuffer>());
}
}
}
try {
scheduleJob(platId);
} catch (SchedulerException e) {
// Job probably already exists
log.error("Unable to reschedule job " + platId, e);
}
if (filter) {
return;
}
} catch (PlatformNotFoundException e) {
log.error("Entity ID invalid: " + e);
}
}
sendEmail(addresses, subject, body, htmlBody, new Integer(priority));
}
private void scheduleJob(Integer platId) throws SchedulerException {
// Create new job name with the appId
String name = EmailFilterJob.class.getName() + platId + "Job";
Scheduler scheduler = Bootstrap.getBean(Scheduler.class);
synchronized (SCHEDULER_LOCK) {
Trigger[] triggers = scheduler.getTriggersOfJob(name, JOB_GROUP);
if (triggers.length == 0) {
JobDetail jobDetail = new JobDetail(name, JOB_GROUP, EmailFilterJob.class);
String appIdStr = platId.toString();
jobDetail.getJobDataMap().put(EmailFilterJob.APP_ID, appIdStr);
// XXX: Make this time configurable?
GregorianCalendar next = new GregorianCalendar();
next.add(GregorianCalendar.MINUTE, 5);
SimpleTrigger t = new SimpleTrigger(name + "Trigger", JOB_GROUP, next.getTime());
Date nextfire = scheduler.scheduleJob(jobDetail, t);
log.debug("Will queue alerts for platform " + platId + " until " + nextfire);
} else {
// Already scheduled, there will only be a single trigger.
log.debug("Already queing alerts for platform " + platId + ", will fire at " +
triggers[0].getNextFireTime());
}
}
}
}