com.redhat.rhn.taskomatic.task.DailySummary.java Source code

Java tutorial

Introduction

Here is the source code for com.redhat.rhn.taskomatic.task.DailySummary.java

Source

/**
 * Copyright (c) 2009--2014 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.Mail;
import com.redhat.rhn.common.messaging.SmtpMail;
import com.redhat.rhn.domain.org.OrgFactory;
import com.redhat.rhn.frontend.dto.ActionMessage;
import com.redhat.rhn.frontend.dto.AwolServer;
import com.redhat.rhn.frontend.dto.OrgIdWrapper;
import com.redhat.rhn.frontend.dto.ReportingUser;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.StopWatch;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 * DailySummary task.
 * sends daily report of stats. reaps org suggestions
 * from rhnDailySummaryQueue. Not very "daily" since it runs every
 * 30 seconds.  Need to look at RHN::DailySummaryEngine.  This task
 * queues org emails, mails queued emails, then dequeues the emails.
 * @version $Rev$
 */
public class DailySummary extends RhnJavaJob {

    private static final int HEADER_SPACER = 10;
    private static final int ERRATA_SPACER = 4;
    private static final String ERRATA_UPDATE = "Errata Update";
    private static final String ERRATA_INDENTION = StringUtils.repeat(" ", ERRATA_SPACER);

    private Mail mail;

    /**
     * Default constructor
     */
    public DailySummary() {
        this(new SmtpMail());
    }

    /**
     * Constructor takes in a Mailer
     * @param mailer mailer if you don't want to use the default SmtpMail
     */
    public DailySummary(Mail mailer) {
        mail = mailer;
    }

    /**
     * {@inheritDoc}
     */
    public void execute(JobExecutionContext ctxIn) throws JobExecutionException {
        SelectMode m = ModeFactory.getMode(TaskConstants.MODE_NAME, TaskConstants.TASK_QUERY_DAILY_SUMMARY_QUEUE);
        List results = m.execute();

        OrgIdWrapper oiw = null;
        for (Iterator itr = results.iterator(); itr.hasNext();) {
            try {
                oiw = (OrgIdWrapper) itr.next();
                if (log.isDebugEnabled()) {
                    log.debug("dealing with org: " + oiw.toLong());
                }
                queueOrgEmails(oiw.toLong());
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            } finally {
                try {
                    dequeueOrg(oiw.toLong());
                    if (log.isDebugEnabled()) {
                        log.debug("org " + oiw.toLong() + " removed from queue");
                    }
                } finally {
                    HibernateFactory.commitTransaction();
                    HibernateFactory.closeSession();
                }
            }
        }
    }

    /**
     * DO NOT CALL FROM OUTSIDE THIS CLASS. Removes the orgs from the queue
     * table.
     * @param orgId Org Id to be dequeued.
     * @return # of orgs dequeued
     */
    public int dequeueOrg(Long orgId) {
        WriteMode m = ModeFactory.getWriteMode(TaskConstants.MODE_NAME,
                TaskConstants.TASK_QUERY_DEQUEUE_DAILY_SUMMARY);
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("org_id", orgId);
        return m.executeUpdate(params);
    }

    /**
     * DO NOT CALL FROM OUTSIDE THIS CLASS. Queues up the Org Emails for
     * mailing.
     * @param orgId Org Id to be processed.
     */
    public void queueOrgEmails(Long orgId) {
        SelectMode m = ModeFactory.getMode(TaskConstants.MODE_NAME, TaskConstants.TASK_QUERY_USERS_WANTING_REPORTS);
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("org_id", orgId);

        StopWatch watch = new StopWatch();
        watch.start();
        List users = m.execute(params);
        for (Iterator itr = users.iterator(); itr.hasNext();) {
            ReportingUser ru = (ReportingUser) itr.next();
            // run_user
            List awol = getAwolServers(ru.idAsLong());
            // send email
            List actions = getActionInfo(ru.idAsLong());
            if ((awol == null || awol.size() == 0) && (actions == null || actions.size() == 0)) {
                log.debug("Skipping ORG " + orgId + " because daily summary info has " + "changed");
                continue;
            }

            String awolMsg = renderAwolServersMessage(awol);
            String actionMsg = renderActionsMessage(actions);

            String emailMsg = prepareEmail(ru.getLogin(), ru.getAddress(), awolMsg, actionMsg);

            LocalizationService ls = LocalizationService.getInstance();
            mail.setSubject(ls.getMessage("dailysummary.email.subject", ls.formatShortDate(new Date())));
            mail.setRecipient(ru.getAddress());

            if (log.isDebugEnabled()) {
                log.debug("Sending email to [" + ru.getAddress() + "]");
            }

            mail.setBody(emailMsg);
            TaskHelper.sendMail(mail, log);
        }
        watch.stop();
        if (log.isDebugEnabled()) {
            log.debug("queued emails of org of " + users.size() + " users in " + watch.getTime() + "ms");
        }
    }

    /**
     * DO NOT CALL FROM OUTSIDE THIS CLASS. Returns the list of awol servers.
     * @param uid User id whose awol servers are sought.
     * @return the list of recent awol servers.
     */
    public List getAwolServers(Long uid) {
        SelectMode m = ModeFactory.getMode(TaskConstants.MODE_NAME, TaskConstants.TASK_QUERY_USERS_AWOL_SERVERS);
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("user_id", uid);
        params.put("checkin_threshold", Config.get().getInteger(ConfigDefaults.SYSTEM_CHECKIN_THRESHOLD));

        return m.execute(params);
    }

    /**
     * DO NOT CALL FROM OUTSIDE THIS CLASS. Returns the list of recent actions.
     * @param uid User id whose recent actions are sought.
     * @return the list of recent actions.
     */
    public List getActionInfo(Long uid) {
        SelectMode m = ModeFactory.getMode(TaskConstants.MODE_NAME, TaskConstants.TASK_QUERY_GET_ACTION_INFO);
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("user_id", uid);

        return m.execute(params);
    }

    /**
     * DO NOT CALL FROM OUTSIDE THIS CLASS. Renders the awol servers message
     * @param servers list of awol servers
     * @return the awol servers message
     */
    public String renderAwolServersMessage(List servers) {
        if (servers == null || servers.isEmpty()) {
            return "";
        }
        /*
         * The Awol message is going to be a table containing a list of systems
         * that have gone AWOL.
         *
         * All the calculation crap for tables will be done...  how many spaces
         * between columns and the column width for the given data.
         * This means that we will read through the data twice, once to find the
         * longest entries and again to build the return string.
         *
         * Since this will be going in an email, if the receiver doesn't use
         * monospace fonts *ever* than all this calculation is for nothing.
         */
        LocalizationService ls = LocalizationService.getInstance();
        String sid = ls.getMessage("taskomatic.daily.sid"); //System Id column
        String sname = ls.getMessage("taskomatic.daily.systemname"); //System Name column
        String checkin = ls.getMessage("taskomatic.daily.checkin"); //Last Checkin column

        //First we need to figure out how long the width of the columns should be.
        int minDiff = 4; //this is the minimum spaces between header elements
        int sidLength = sid.length() + minDiff;
        int snameLength = sid.length() + minDiff;

        //Find the longest entry in the table for both sid and sname.
        for (Iterator itr = servers.iterator(); itr.hasNext();) {
            AwolServer as = (AwolServer) itr.next();
            String currentId = as.getId().toString();
            if (currentId.length() >= sidLength) {
                //extra space so the longest entry doesn't connect to the next column
                sidLength = currentId.length() + 1;
            }
            String currentName = as.getName();
            if (currentName.length() >= snameLength) {
                //extra space so the longest entry doesn't connect to the next column
                snameLength = currentName.length() + 1;
            }
        }

        //render the header--  System Id        System Name        LastCheckin
        StringBuilder buf = new StringBuilder();
        buf.append(sid);
        buf.append(StringUtils.repeat(" ", sidLength - sid.length()));
        buf.append(sname);
        buf.append(StringUtils.repeat(" ", snameLength - sname.length()));
        buf.append(checkin);
        buf.append("\n");

        //Now render the data in the table
        for (Iterator itr = servers.iterator(); itr.hasNext();) {
            AwolServer as = (AwolServer) itr.next();
            String currentId = as.getId().toString();
            buf.append(currentId);
            buf.append(StringUtils.repeat(" ", sidLength - currentId.length()));
            String currentName = as.getName();
            buf.append(currentName);
            buf.append(StringUtils.repeat(" ", snameLength - currentName.length()));
            buf.append(as.getCheckin());
            buf.append("\n");
        }

        //Lastly, create the url for the link in the email.
        StringBuilder url = new StringBuilder();
        if (Config.get().getBoolean(ConfigDefaults.SSL_AVAILABLE)) {
            url.append("https://");
        } else {
            url.append("http://");
        }
        url.append(getHostname());
        url.append("/rhn/systems/Inactive.do");

        return LocalizationService.getInstance().getMessage("taskomatic.msg.awolservers", buf.toString(), url);
    }

    /**
     * DO NOT CALL FROM OUTSIDE THIS CLASS. Renders the actions email message
     * @param actions list of recent actions
     * @return the actions email message
     */
    public String renderActionsMessage(List<ActionMessage> actions) {

        int longestActionLength = HEADER_SPACER;
        int longestStatusLength = 0;
        StringBuilder hdr = new StringBuilder();
        StringBuilder body = new StringBuilder();
        StringBuilder legend = new StringBuilder();
        StringBuilder msg = new StringBuilder();
        LinkedHashSet<String> statusSet = new LinkedHashSet();
        TreeMap<String, Map<String, Integer>> nonErrataActions = new TreeMap();
        TreeMap<String, Map<String, Integer>> errataActions = new TreeMap();
        TreeMap<String, String> errataSynopsis = new TreeMap();

        legend.append(LocalizationService.getInstance().getMessage("taskomatic.daily.errata"));
        legend.append("\n\n");

        for (ActionMessage am : actions) {

            if (!statusSet.contains(am.getStatus())) {
                statusSet.add(am.getStatus());
                if (am.getStatus().length() > longestStatusLength) {
                    longestStatusLength = am.getStatus().length();
                }
            }

            if (am.getType().equals(ERRATA_UPDATE)) {
                String advisoryKey = ERRATA_INDENTION + am.getAdvisory();

                if (!errataActions.containsKey(advisoryKey)) {
                    errataActions.put(advisoryKey, new HashMap());
                    if (advisoryKey.length() + HEADER_SPACER > longestActionLength) {
                        longestActionLength = advisoryKey.length() + HEADER_SPACER;
                    }
                }
                Map<String, Integer> counts = errataActions.get(advisoryKey);
                counts.put(am.getStatus(), am.getCount());

                if (am.getAdvisory() != null && !errataSynopsis.containsKey(am.getAdvisory())) {
                    errataSynopsis.put(am.getAdvisory(), am.getSynopsis());
                }
            } else {
                if (!nonErrataActions.containsKey(am.getType())) {
                    nonErrataActions.put(am.getType(), new HashMap());
                    if (am.getType().length() + HEADER_SPACER > longestActionLength) {
                        longestActionLength = am.getType().length() + HEADER_SPACER;
                    }
                }
                Map<String, Integer> counts = nonErrataActions.get(am.getType());
                counts.put(am.getStatus(), am.getCount());
            }

        }

        hdr.append(StringUtils.repeat(" ", longestActionLength));
        for (String status : statusSet) {
            hdr.append(status + StringUtils.repeat(" ", (longestStatusLength + ERRATA_SPACER) - status.length()));
        }

        if (!errataActions.isEmpty()) {
            body.append(ERRATA_UPDATE + ":" + "\n");
        }
        StringBuffer formattedErrataActions = renderActionTree(longestActionLength, longestStatusLength, statusSet,
                errataActions);
        body.append(formattedErrataActions);

        for (String advisory : errataSynopsis.keySet()) {
            legend.append(ERRATA_INDENTION + advisory + ERRATA_INDENTION + errataSynopsis.get(advisory) + "\n");
        }

        StringBuffer formattedNonErrataActions = renderActionTree(longestActionLength, longestStatusLength,
                statusSet, nonErrataActions);
        body.append(formattedNonErrataActions);

        // finally put all this together
        msg.append(hdr.toString());
        msg.append("\n");
        msg.append(body.toString());
        msg.append("\n\n");
        if (!errataSynopsis.isEmpty()) {
            msg.append(legend.toString());
        }
        return msg.toString();
    }

    private StringBuffer renderActionTree(int longestActionLength, int longestStatusLength,
            LinkedHashSet<String> statusSet, TreeMap<String, Map<String, Integer>> actionTree) {
        StringBuffer formattedActions = new StringBuffer();
        for (String actionName : actionTree.keySet()) {
            formattedActions
                    .append(actionName + StringUtils.repeat(" ", (longestActionLength - (actionName.length()))));
            for (String status : statusSet) {
                Map<String, Integer> counts = actionTree.get(actionName);
                Integer theCount = counts.get(status);
                if (counts.containsKey(status)) {
                    theCount = counts.get(status);
                } else {
                    theCount = 0;
                }
                formattedActions.append(theCount);
                formattedActions.append(StringUtils.repeat(" ",
                        longestStatusLength + ERRATA_SPACER - theCount.toString().length()));
            }
            formattedActions.append("\n");
        }
        return formattedActions;
    }

    /**
     * DO NOT CALL FROM OUTSIDE THIS CLASS. Prepares the email message string
     * @param login users login
     * @param email email address
     * @param awolMsg the awol servers msg
     * @param actionMsg the recent actions message
     * @return the email message string
     */
    public String prepareEmail(String login, String email, String awolMsg, String actionMsg) {

        LocalizationService ls = LocalizationService.getInstance();
        String[] args = new String[7];
        args[0] = login;
        args[1] = ls.formatDate(new Date());
        args[2] = actionMsg;
        args[3] = awolMsg;
        args[4] = getHostname();
        // why the hell are these in OrgFactory?
        args[5] = OrgFactory.EMAIL_FOOTER.getValue();
        args[6] = OrgFactory.EMAIL_ACCOUNT_INFO.getValue();
        String msg = ls.getMessage("dailysummary.email.body", (Object[]) args);

        // wow, what an ugly @$$ hack, but this requires rewriting
        // the email templating engine which kinda sucks.
        msg = StringUtils.replace(msg, "<login />", login);
        return StringUtils.replace(msg, "<email-address />", email);
    }

    private String getHostname() {
        return ConfigDefaults.get().getHostname();
    }
}