org.hyperic.hq.bizapp.server.action.email.EmailAction.java Source code

Java tutorial

Introduction

Here is the source code for org.hyperic.hq.bizapp.server.action.email.EmailAction.java

Source

/*
 * 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.action.email;

import java.io.File;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.Set;

import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperic.hq.appdef.shared.AppdefEntityID;
import org.hyperic.hq.appdef.shared.AppdefUtil;
import org.hyperic.hq.application.Scheduler;
import org.hyperic.hq.authz.server.session.AuthzSubject;
import org.hyperic.hq.authz.server.session.Resource;
import org.hyperic.hq.authz.server.session.ResourceDAO;
import org.hyperic.hq.authz.shared.AuthzSubjectManager;
import org.hyperic.hq.bizapp.shared.EmailManager;
import org.hyperic.hq.bizapp.shared.action.EmailActionConfig;
import org.hyperic.hq.common.shared.ServerConfigManager;
import org.hyperic.hq.context.Bootstrap;
import org.hyperic.hq.escalation.server.session.Escalatable;
import org.hyperic.hq.escalation.server.session.EscalationStateChange;
import org.hyperic.hq.escalation.server.session.PerformsEscalations;
import org.hyperic.hq.events.ActionExecuteException;
import org.hyperic.hq.events.ActionExecutionInfo;
import org.hyperic.hq.events.ActionInterface;
import org.hyperic.hq.events.AlertDefinitionInterface;
import org.hyperic.hq.events.AlertInterface;
import org.hyperic.hq.events.EventConstants;
import org.hyperic.hq.events.InvalidActionDataException;
import org.hyperic.hq.events.Notify;
import org.hyperic.hq.events.server.session.AlertRegulator;
import org.hyperic.hq.hqu.RenditServer;
import org.hyperic.hq.measurement.MeasurementNotFoundException;
import org.hyperic.hq.stats.ConcurrentStatsWriter;
import org.hyperic.util.ConfigPropertyException;
import org.hyperic.util.StringUtil;
import org.hyperic.util.config.ConfigResponse;

public class EmailAction extends EmailActionConfig implements ActionInterface, Notify {
    protected static String baseUrl = null;
    private static final int _alertThreshold;
    private static final List<EmailObj> _emails = new ArrayList<EmailObj>();
    private static EmailRecipient[] _emailAddrs;
    // Evaluate number of notifications each period when AlertThreshold is
    // enabled to potentially toggle/block all notifications for 1 - Many
    // THRESHOLD_WINDOW(s).  Notifications are only sent out after each period
    // when mechanism is turned on.
    // Matched it up with ConcurrentStatsCollector in order to obtain easy
    // stats on a deployment's activity
    private static final long EVALUATION_PERIOD = ConcurrentStatsWriter.WRITE_PERIOD * 1000;
    // evaluation window to continue and block notifications or
    // toggle back to regular operation
    private static final int THRESHOLD_WINDOW = 10 * 60 * 1000;

    private static final Log _log = LogFactory.getLog(EmailAction.class);
    private static final String BUNDLE = "org.hyperic.hq.bizapp.Resources";

    private ResourceDAO resourceDAO = Bootstrap.getBean(ResourceDAO.class);

    static {
        ServerConfigManager sConf = Bootstrap.getBean(ServerConfigManager.class);
        int tmp = 0;
        try {
            final Properties props = sConf.getConfig();
            tmp = Integer.parseInt(props.getProperty("HQ_ALERT_THRESHOLD", "0"));
            String[] array = props.getProperty("HQ_ALERT_THRESHOLD_EMAILS", "").split(",");
            _emailAddrs = new EmailRecipient[array.length];
            for (int i = 0; i < array.length; i++) {
                try {
                    _emailAddrs[i] = new EmailRecipient(new InternetAddress(array[i]), false);
                } catch (AddressException e) {
                    _log.debug(e.getMessage(), e);
                }
            }

            if (_emailAddrs.length == 0) {
                tmp = 0;
            }
        } catch (NumberFormatException e) {
            _log.debug(e.getMessage(), e);
        } catch (ConfigPropertyException e) {
            _log.debug(e.getMessage(), e);
        }

        _alertThreshold = tmp;

        if (_alertThreshold > 0) {
            Bootstrap.getBean(Scheduler.class).scheduleWithFixedDelay(new ThresholdWorker(),
                    Scheduler.NO_INITIAL_DELAY, EVALUATION_PERIOD);
        }
    }

    public EmailAction() {
    }

    protected final AuthzSubjectManager getSubjMan() {
        return Bootstrap.getBean(AuthzSubjectManager.class);
    }

    private String renderTemplate(String filename, Map<?, ?> params) {
        StringWriter output = new StringWriter();
        try {
            File templateDir = Bootstrap.getResource("WEB-INF/alertTemplates").getFile();
            File templateFile = new File(templateDir, filename);
            Bootstrap.getBean(RenditServer.class).renderTemplate(templateFile, params, output);
            if (_log.isDebugEnabled()) {
                _log.debug("Template rendered\n" + output.toString());
            }
        } catch (Exception e) {
            _log.warn("Unable to render template", e);
        }
        return output.toString();
    }

    private String createSubject(AlertDefinitionInterface alertdef, AlertInterface alert, Resource resource,
            ActionExecutionInfo action, String status) {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("resource", resource);
        params.put("alertDef", alertdef);
        params.put("alert", alert);
        params.put("action", action);
        params.put("status", status);
        params.put("isSms", new Boolean(isSms()));
        return renderTemplate("subject.gsp", params);
    }

    private String createText(AlertDefinitionInterface alertdef, ActionExecutionInfo info, Resource resource,
            AlertInterface alert, String templateName, AuthzSubject user) throws MeasurementNotFoundException {
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("alertDef", alertdef);
        params.put("alert", alert);
        params.put("action", info);
        params.put("resource", resource);
        params.put("user", user);
        return renderTemplate(templateName, params);
    }

    private AppdefEntityID getResource(AlertDefinitionInterface def) {
        return AppdefUtil.newAppdefEntityId(def.getResource());
    }

    public String execute(AlertInterface alert, ActionExecutionInfo info) throws ActionExecuteException {
        try {
            if (!Bootstrap.getBean(AlertRegulator.class).alertNotificationsAllowed()) {
                return ResourceBundle.getBundle(BUNDLE).getString("action.email.error.notificationDisabled");
            }
            Map<EmailRecipient, AuthzSubject> addrs = lookupEmailAddr();
            if (addrs.isEmpty()) {
                return ResourceBundle.getBundle(BUNDLE).getString("action.email.error.noEmailAddress");
            }
            AlertDefinitionInterface alertDef = alert.getAlertDefinitionInterface();
            AppdefEntityID appEnt = getResource(alertDef);
            String logStr = "No notifications sent, see server log for details.";
            if (appEnt != null) {
                Resource resource = alertDef.getResource();
                if (resource != null && !resource.isInAsyncDeleteState()) {
                    String[] body = new String[addrs.size()];
                    String[] htmlBody = new String[addrs.size()];
                    EmailRecipient[] to = (EmailRecipient[]) addrs.keySet()
                            .toArray(new EmailRecipient[addrs.size()]);
                    for (int i = 0; i < to.length; i++) {
                        AuthzSubject user = (AuthzSubject) addrs.get(to[i]);
                        if (to[i].useHtml()) {
                            htmlBody[i] = createText(alertDef, info, resource, alert, "html_email.gsp", user);
                        }
                        body[i] = createText(alertDef, info, resource, alert,
                                isSms() ? "sms_email.gsp" : "text_email.gsp", user);
                    }
                    final String subject = createSubject(alertDef, alert, resource, info, "");
                    sendAlert(appEnt, to, subject, body, htmlBody, alertDef.getPriority(),
                            alertDef.isNotifyFiltered());
                    StringBuffer result = getLog(to);
                    logStr = result.toString();
                } else {
                    _log.warn("No resource for alert definition " + alertDef.getId()
                            + ", perhaps the resource was deleted?  Email notification will not be sent.");
                }
            } else {
                _log.warn("No appdef entity ID for alert definition " + alertDef.getId()
                        + ", perhaps the related platform was deleted?  Email notification will not be sent.");
            }
            return logStr;
        } catch (Exception e) {
            throw new ActionExecuteException(e);
        }
    }

    protected StringBuffer getLog(EmailRecipient[] to) {
        StringBuffer result = new StringBuffer(isSms() ? "SMS" : "Notified");
        // XXX: Should get this strings into a resource file
        switch (getType()) {
        case TYPE_USERS:
            result.append(" users: ");
            break;
        default:
        case TYPE_EMAILS:
            result.append(": ");
            break;
        }

        for (int i = 0; i < to.length; i++) {
            result.append(to[i].getAddress().getPersonal());
            if (i < to.length - 1) {
                result.append(", ");
            }
        }

        return result;
    }

    protected Map<EmailRecipient, AuthzSubject> lookupEmailAddr() throws ActionExecuteException {
        // First, look up the addresses
        HashSet<InternetAddress> prevRecipients = new HashSet<InternetAddress>();
        Map<EmailRecipient, AuthzSubject> validRecipients = new HashMap<EmailRecipient, AuthzSubject>();
        for (Iterator<?> it = getUsers().iterator(); it.hasNext();) {
            try {
                InternetAddress addr;
                boolean useHtml = false;
                AuthzSubject who = null;
                switch (getType()) {
                case TYPE_USERS:
                    Integer uid = (Integer) it.next();
                    who = getSubjMan().getSubjectById(uid);
                    if (who == null) {
                        _log.warn("User not found: " + uid);
                        continue;
                    }
                    addr = (isSms()) ? new InternetAddress(who.getSMSAddress())
                            : new InternetAddress(who.getEmailAddress());
                    addr.setPersonal(who.getName());
                    useHtml = isSms() ? false : who.isHtmlEmail();
                    break;
                default:
                case TYPE_EMAILS:
                    addr = new InternetAddress((String) it.next(), true);
                    addr.setPersonal(addr.getAddress());
                    break;
                }
                // Don't send duplicate notifications
                if (prevRecipients.add(addr)) {
                    validRecipients.put(new EmailRecipient(addr, useHtml), who);
                }
            } catch (AddressException e) {
                _log.warn("Mail address invalid", e);
                continue;
            } catch (UnsupportedEncodingException e) {
                _log.warn("Username encoding error", e);
                continue;
            } catch (Exception e) {
                _log.warn("Email lookup failed");
                _log.debug("Email lookup failed", e);
                continue;
            }
        }
        return validRecipients;
    }

    public void setParentActionConfig(AppdefEntityID ent, ConfigResponse cfg) throws InvalidActionDataException {
        init(cfg);
    }

    public void send(Escalatable alert, EscalationStateChange change, String message, Set<InternetAddress> notified)
            throws ActionExecuteException {
        PerformsEscalations def = alert.getDefinition();

        Map<EmailRecipient, AuthzSubject> addrs = lookupEmailAddr();

        for (Iterator<Entry<EmailRecipient, AuthzSubject>> it = addrs.entrySet().iterator(); it.hasNext();) {
            Entry<EmailRecipient, AuthzSubject> entry = it.next();
            EmailRecipient rec = entry.getKey();
            // Don't notify again if already notified
            if (notified.contains(rec.getAddress())) {
                it.remove();
                continue;
            }
            rec.setHtml(false);
            notified.add(rec.getAddress());
        }
        AlertDefinitionInterface defInfo = def.getDefinitionInfo();
        String[] messages = new String[addrs.size()];
        Arrays.fill(messages, message);

        EmailRecipient[] to = (EmailRecipient[]) addrs.keySet().toArray(new EmailRecipient[addrs.size()]);

        AppdefEntityID appEnt = getResource(defInfo);

        Resource resource = resourceDAO.findByInstanceId(appEnt.getAuthzTypeId(), appEnt.getId());

        final String subject = createSubject(defInfo, alert.getAlertInfo(), resource, null,
                change.getDescription());
        sendAlert(getResource(defInfo), to, subject, messages, messages, defInfo.getPriority(), false);
    }

    private static void sendAlert(AppdefEntityID appEnt, EmailRecipient[] to, String subject, String[] body,
            String[] htmlBody, int priority, boolean notifyFiltered) {
        if (_alertThreshold <= 0) {
            final boolean debug = _log.isDebugEnabled();
            if (debug) {
                EmailObj obj = new EmailObj(appEnt, to, subject, body, htmlBody, priority, notifyFiltered);
                debug(obj);
            }
            getEmailMan().sendAlert(appEnt, to, subject, body, htmlBody, priority, notifyFiltered);
        } else {
            synchronized (_emails) {
                EmailObj obj = new EmailObj(appEnt, to, subject, body, htmlBody, priority, notifyFiltered);
                _emails.add(obj);
            }
        }
    }

    private static final EmailManager getEmailMan() {
        return Bootstrap.getBean(EmailManager.class);
    }

    private static final long now() {
        return System.currentTimeMillis();
    }

    private static void debug(EmailObj obj) {
        final boolean debug = _log.isDebugEnabled();
        if (debug) {
            final String msg = "Sending alert with info -> " + obj.getAppEnt().getID() + ':'
                    + StringUtil.implode(Arrays.asList(obj.getTo()), ",") + ':' + obj.getSubject() + ':'
                    + obj.getPriority();
            _log.debug(msg);
        }
    }

    private static class ThresholdWorker implements Runnable {
        private long _lastEmailTime = -1l;
        private boolean _inThresholdWindow = false;
        private String _endMsg = null, _beginMsg = null, _continueMsg = null, _endSubject = null,
                _beginSubject = null, _continueSubject = null;

        public synchronized void run() {
            try {
                List<EmailObj> toEmail = null;
                synchronized (_emails) {
                    if (_emails.size() == 0) {
                        return;
                    }
                    toEmail = new ArrayList<EmailObj>(_emails);
                    _emails.clear();
                }
                if (!_inThresholdWindow) {
                    _inThresholdWindow = (toEmail.size() >= _alertThreshold) ? true : false;
                }
                if (_inThresholdWindow && lastEmailWithinThresholdWindow()) {
                    // if we are already in a threshold window then there is
                    // nothing to do until the window has ended
                    // just drop all emails
                    if (_log.isDebugEnabled()) {
                        _log.debug("In Threshold Window, dropping " + toEmail.size() + " email(s)");
                    }
                    return;
                } else if (_inThresholdWindow && _lastEmailTime == -1l) {
                    _lastEmailTime = now();
                    sendRollupEmail(true, toEmail.size());
                    return;
                } else if (_inThresholdWindow && !lastEmailWithinThresholdWindow()) {
                    // this means that the threshold window has ended
                    // Need to send an email notifying the users that it has
                    // ended -OR- that it will continue
                    _inThresholdWindow = (toEmail.size() >= _alertThreshold) ? true : false;
                    sendRollupEmail(false, toEmail.size());
                    _lastEmailTime = _inThresholdWindow ? now() : -1l;
                    return;
                } else {
                    // send all emails, alert storm is not in affect
                    for (final EmailObj obj : toEmail) {
                        sendFilteredEmail(obj);
                    }
                    return;
                }
            } catch (Throwable e) {
                _log.error(e.getMessage(), e);
                return;
            }
        }

        private void sendFilteredEmail(EmailObj obj) {
            debug(obj);
            getEmailMan().sendAlert(obj.getAppEnt(), obj.getTo(), obj.getSubject(), obj.getBody(),
                    obj.getHtmlBody(), obj.getPriority(), obj.isNotifyFiltered());
        }

        private final boolean lastEmailWithinThresholdWindow() {
            return (_lastEmailTime > (now() - THRESHOLD_WINDOW)) ? true : false;
        }

        private final void sendRollupEmail(boolean startWindow, int notificationCount) throws AddressException {
            String msg = "", subject = "";
            if (startWindow) {
                msg = getWindowStartMsg(notificationCount);
                subject = getWindowStartSubject();
            } else if (_inThresholdWindow) {
                msg = getWindowContinueMsg(notificationCount);
                subject = getWindowContinueSubject();
            } else {
                msg = getWindowEndMsg();
                subject = getWindowEndSubject();
            }
            final EmailRecipient[] recipients = getEmailRecipients();
            final String[] message = new String[recipients.length];
            for (int i = 0; i < recipients.length; i++) {
                message[i] = msg;
            }
            if (_log.isDebugEnabled()) {
                _log.debug("Sending Threshold Email to " + StringUtil.implode(Arrays.asList(recipients), ",") + ','
                        + " msg: " + msg);
            }
            getEmailMan().sendEmail(recipients, subject, message, message,
                    new Integer(EventConstants.PRIORITY_HIGH));
        }

        private final String getWindowEndSubject() {
            if (_endSubject != null) {
                return _endSubject;
            }
            _endSubject = ResourceBundle.getBundle(BUNDLE).getString("alert.threshold.subject.end.message");
            return _endSubject;
        }

        private final String getWindowContinueSubject() {
            if (_continueSubject != null) {
                return _continueSubject;
            }
            _continueSubject = ResourceBundle.getBundle(BUNDLE)
                    .getString("alert.threshold.subject.continue.message");
            return _continueSubject;
        }

        private final String getWindowStartSubject() {
            if (_beginSubject != null) {
                return _beginSubject;
            }
            _beginSubject = ResourceBundle.getBundle(BUNDLE).getString("alert.threshold.subject.begin.message");
            return _beginSubject;
        }

        private final String getWindowEndMsg() {
            if (_endMsg != null) {
                return _endMsg;
            }
            _endMsg = ResourceBundle.getBundle(BUNDLE).getString("alert.threshold.end.message");
            return _endMsg;
        }

        private final String getWindowContinueMsg(int notificationCount) {
            if (_continueMsg != null) {
                return _continueMsg.replaceAll("\\{0\\}", notificationCount + "");
            }
            _continueMsg = ResourceBundle.getBundle(BUNDLE).getString("alert.threshold.continue.message");
            _continueMsg = _continueMsg.replaceAll("\\{1\\}", EVALUATION_PERIOD / 1000 + "").replaceAll("\\{2\\}",
                    THRESHOLD_WINDOW / 60000 + "");
            return _continueMsg.replaceAll("\\{0\\}", notificationCount + "");
        }

        private final String getWindowStartMsg(int notificationCount) {
            if (_beginMsg != null) {
                return _beginMsg.replaceAll("\\{0\\}", notificationCount + "").replaceAll("\\{2\\}",
                        _alertThreshold + "");
            }
            _beginMsg = ResourceBundle.getBundle(BUNDLE).getString("alert.threshold.begin.message");
            _beginMsg = _beginMsg.replaceAll("\\{1\\}", EVALUATION_PERIOD / 1000 + "").replaceAll("\\{3\\}",
                    THRESHOLD_WINDOW / 60000 + "");
            return _beginMsg.replaceAll("\\{0\\}", notificationCount + "").replaceAll("\\{2\\}",
                    _alertThreshold + "");
        }

        private EmailRecipient[] getEmailRecipients() throws AddressException {
            return _emailAddrs;
        }
    }

    private static class EmailObj {
        private final AppdefEntityID _appEnt;
        private final EmailRecipient[] _to;
        private final String _subject;
        private final String[] _body;
        private final String[] _htmlBody;
        private final int _priority;
        private final boolean _notifyFiltered;

        public EmailObj(AppdefEntityID appEnt, EmailRecipient[] to, String subject, String[] body,
                String[] htmlBody, int priority, boolean notifyFiltered) {
            _appEnt = appEnt;
            _to = to;
            _subject = subject;
            _body = body;
            _htmlBody = htmlBody;
            _priority = priority;
            _notifyFiltered = notifyFiltered;
        }

        public AppdefEntityID getAppEnt() {
            return _appEnt;
        }

        public EmailRecipient[] getTo() {
            return _to;
        }

        public String getSubject() {
            return _subject;
        }

        public String[] getBody() {
            return _body;
        }

        public String[] getHtmlBody() {
            return _htmlBody;
        }

        public int getPriority() {
            return _priority;
        }

        public boolean isNotifyFiltered() {
            return _notifyFiltered;
        }
    }
}