/* * 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()); } } } }