/** * Copyright (c) 2009--2015 Red Hat, Inc. * * This software is licensed to you under the GNU General Public License, * version 2 (GPLv2). There is NO WARRANTY for this software, express or * implied, including the implied warranties of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 * along with this software; if not, see * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. * * Red Hat trademarks are not licensed under GPLv2. No permission is * granted to use or replicate Red Hat trademarks that are incorporated * in this software or its documentation. */ package com.redhat.rhn.taskomatic.task; import com.redhat.rhn.common.conf.Config; import com.redhat.rhn.common.conf.ConfigDefaults; import com.redhat.rhn.common.db.datasource.ModeFactory; import com.redhat.rhn.common.db.datasource.SelectMode; import com.redhat.rhn.common.db.datasource.WriteMode; import com.redhat.rhn.common.hibernate.HibernateFactory; import com.redhat.rhn.common.localization.LocalizationService; import com.redhat.rhn.common.messaging.JavaMailException; import com.redhat.rhn.common.messaging.Mail; import com.redhat.rhn.common.messaging.SmtpMail; import com.redhat.rhn.domain.errata.Errata; import com.redhat.rhn.domain.errata.impl.PublishedErrata; import com.redhat.rhn.domain.org.OrgFactory; import org.apache.commons.lang.StringUtils; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; /** * This is a port of the ErrataEngine taskomatic task * * @version $Rev.$ */ public class ErrataMailer extends RhnJavaJob { /** * {@inheritDoc} */ public void execute(JobExecutionContext context) throws JobExecutionException { List results = getErrataToProcess(); if (results == null || results.size() == 0) { if (log.isDebugEnabled()) { log.debug("No errata found...exiting"); } } else { if (log.isDebugEnabled()) { log.debug("=== Queued up " + results.size() + " errata"); } for (Iterator iter = results.iterator(); iter.hasNext();) { Map row = (Map) iter.next(); Long errataId = (Long) row.get("errata_id"); Long orgId = (Long) row.get("org_id"); Long channelId = (Long) row.get("channel_id"); markErrataDone(errataId, orgId, channelId); if (OrgFactory.lookupById(orgId).getOrgConfig().isErrataEmailsEnabled()) { if (log.isDebugEnabled()) { log.debug("Processing errata " + errataId + " for org " + orgId); } try { sendEmails(errataId, orgId, channelId); if (log.isDebugEnabled()) { log.debug("Finished errata " + errataId + " for org " + orgId); } } catch (JavaMailException e) { log.error("Error sending mail", e); } } else { if (log.isDebugEnabled()) { log.debug("Errata notifications disabled for whole org " + orgId + " => skipping " + errataId); } } } } } protected List getErrataToProcess() { SelectMode select = ModeFactory.getMode(TaskConstants.MODE_NAME, TaskConstants.TASK_QUERY_ERRATAMAILER_FIND_ERRATA); Map<String, Object> params = new HashMap<String, Object>(); params.put("threshold", new Integer(1)); List results = select.execute(params); return results; } private void markErrataDone(Long errataId, Long orgId, Long channelId) { HibernateFactory.getSession(); WriteMode marker = ModeFactory.getWriteMode(TaskConstants.MODE_NAME, TaskConstants.TASK_QUERY_ERRATAMAILER_MARK_ERRATA_DONE); Map<String, Object> params = new HashMap<String, Object>(); params.put("org_id", orgId); params.put("errata_id", errataId); params.put("channel_id", channelId); int rowsUpdated = marker.executeUpdate(params); if (log.isDebugEnabled()) { log.debug("Marked " + rowsUpdated + " rows complete"); } } private void sendEmails(Long errataId, Long orgId, Long channelId) { Errata errata = (Errata) HibernateFactory.getSession().load(PublishedErrata.class, new Long(errataId.longValue())); List orgServers = getOrgRelevantServers(errataId, orgId, channelId); if (orgServers == null || orgServers.size() == 0) { log.debug("No relevant servers found for erratum " + errata.getId() + " in channel " + channelId + " for org " + orgId + " ... skipping."); return; } Map<Long, List> userMap = createUserEmailMap(orgServers); log.info("Found " + userMap.keySet().size() + " user(s) to notify about erratum " + errata.getId() + " in channel " + channelId + " for org " + orgId + "."); for (Long userId : userMap.keySet()) { Map userInfo = getUserInfo(userId); String email = (String) userInfo.get("email"); String login = (String) userInfo.get("login"); List servers = userMap.get(userId); log.info("Notification for user " + login + "(" + userId + ") about " + servers.size() + " relevant server(s)."); String emailBody = formatEmail(login, email, errata, servers); Mail mail = new SmtpMail(); mail.setRecipient(email); mail.setHeader("X-RHN-Info", "Autogenerated mail for " + login); mail.setHeader("Precedence", "first-class"); mail.setHeader("Errors-To", "rhn-bounce" + login + "-" + orgId.toString() + "@rhn.redhat.com"); mail.setBody(emailBody); StringBuilder subject = new StringBuilder(); subject.append(Config.get().getString("web.product_name") + " Errata Alert: "); subject.append(errata.getAdvisory()).append(" - "); subject.append(errata.getSynopsis()); mail.setSubject(subject.toString()); TaskHelper.sendMail(mail, log); } } private Map createUserEmailMap(List orgServersIn) { Map<Long, List> map = new HashMap<Long, List>(); for (Iterator i = orgServersIn.iterator(); i.hasNext();) { Map row = (Map) i.next(); Long userId = (Long) row.get("user_id"); if (!map.containsKey(userId)) { map.put(userId, new ArrayList<Map>()); } map.get(userId).add(row); i.remove(); } return map; } private Map getUserInfo(Long userId) { SelectMode mode = ModeFactory.getMode(TaskConstants.MODE_NAME, TaskConstants.TASK_QUERY_ERRATAMAILER_GET_USERINFO); Map<String, Object> params = new HashMap<String, Object>(); params.put("user_id", userId); return (Map) mode.execute(params).get(0); } protected List getOrgRelevantServers(Long errataId, Long orgId, Long channelId) { SelectMode mode = ModeFactory.getMode(TaskConstants.MODE_NAME, TaskConstants.TASK_QUERY_ERRATAMAILER_GET_RELEVANT_SERVERS); Map<String, Object> params = new HashMap<String, Object>(); params.put("errata_id", errataId); params.put("org_id", orgId); params.put("channel_id", channelId); return mode.execute(params); } private String formatEmail(String login, String email, Errata errata, List servers) { StringBuilder body = new StringBuilder(); //Build the hostname with protocol. Used to create urls for the email. String host; //The protocol from configuration. if (ConfigDefaults.get().isSSLAvailable()) { host = "https://"; } else { host = "http://"; } //Add the hostname host = host + ConfigDefaults.get().getHostname(); //Build the email body body.append(getEmailBodySummary(errata, host)); body.append("\n").append("\n"); body.append(getEmailBodyAffectedSystems(host, servers)); body.append("\n").append("\n"); body.append(getEmailBodyPreferences(host, login, email)); return body.toString(); } private String getEmailBodySummary(Errata errata, String host) { LocalizationService ls = LocalizationService.getInstance(); Object[] args = new Object[8]; //Build the errata details url. StringBuilder buffy = new StringBuilder(); buffy.append(host).append("/rhn/errata/details/Details.do?eid="); buffy.append(errata.getId().toString()); args[0] = buffy.toString(); //Add in the errata information. args[1] = errata.getAdvisoryType() == null ? "" : errata.getAdvisoryType(); args[2] = errata.getAdvisory() == null ? "" : errata.getAdvisory(); args[3] = errata.getSynopsis() == null ? "" : errata.getSynopsis(); args[4] = errata.getTopic() == null ? "" : errata.getTopic(); args[5] = errata.getDescription() == null ? "" : errata.getDescription(); args[6] = errata.getNotes() == null ? "" : errata.getNotes(); args[7] = errata.getRefersTo() == null ? "" : errata.getRefersTo(); return ls.getMessage("email.errata.notification.body.summary", args); } private String getEmailBodyAffectedSystems(String host, List servers) { LocalizationService ls = LocalizationService.getInstance(); //Render the header of the affected systems section along with helpful text. StringBuilder buffy = new StringBuilder(); buffy.append(ls.getMessage("email.errata.notification.body.affectedheader")); buffy.append("\n").append("\n"); //There is one sentence off on its own that deals with whether there are //multiple systems or just one, so this is a separate trans-unit. if (servers.size() == 1) { buffy.append(ls.getMessage("email.errata.notification.body.onesystem")); } else { buffy.append(ls.getMessage("email.errata.notification.body.numsystems", new Object[] {String.valueOf(servers.size())})); } buffy.append("\n").append("\n"); //Now show the table of affected systems and the footer text Object[] args = new Object[2]; //Create the data to show in the table //TODO: I'm just copying over code that was here before, but it // seems to me that we should be printing another column to // the table according to the String Resource bundle. StringWriter writer = new StringWriter(); PrintWriter printWriter = new PrintWriter(writer, true); for (Iterator iter = servers.iterator(); iter.hasNext();) { Map row = (Map) iter.next(); String release = (String) row.get("release"); printWriter.print(release); for (int i = 0; i < (11 - release.length()); i++) { printWriter.print(' '); } String arch = (String) row.get("arch"); printWriter.print(arch); for (int i = 0; i < (11 - arch.length()); i++) { printWriter.print(' '); } printWriter.println((String) row.get("name")); } printWriter.flush(); args[0] = writer.toString(); //URL for the system list args[1] = host + "/rhn/systems/Overview.do"; buffy.append(ls.getMessage("email.errata.notification.body.affected", args)); return buffy.toString(); } private String getEmailBodyPreferences(String host, String login, String email) { LocalizationService ls = LocalizationService.getInstance(); Object[] args = new Object[3]; //URL for user preferences args[0] = host + "/rhn/account/UserPreferences.do"; //custom email footer args[1] = OrgFactory.EMAIL_FOOTER.getValue(); //custom account info args[2] = OrgFactory.EMAIL_ACCOUNT_INFO.getValue(); //This is so ugly! For some reason we support these 'macros' for //account info only. But we made them look like XML tags as if spaces //didn't matter. However, spaces do matter. <sigh /> args[2] = StringUtils.replace(args[2].toString(), "<login />", login); args[2] = StringUtils.replace(args[2].toString(), "<email-address />", email); return ls.getMessage("email.errata.notification.body.preferences", args); } }