hudson.plugins.trackplus.Updater.java Source code

Java tutorial

Introduction

Here is the source code for hudson.plugins.trackplus.Updater.java

Source

/*
 * This file is part of the Track+ application, a
 * tool for project and change management.
 *
 * Copyright (C) 2010 Trackplus
 *
 * Use and distribution of this software is governed by the
 * Track+ license conditions.
 * 
 * $Id: Updater.java 2564 2010-09-15 15:19:49Z adib $
 * 
 */
package hudson.plugins.trackplus;

import hudson.Util;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.Hudson;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.AbstractBuild.DependencyChange;
import hudson.scm.RepositoryBrowser;
import hudson.scm.ChangeLogSet.AffectedFile;
import hudson.scm.ChangeLogSet.Entry;

import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.lang.StringUtils;

import com.trackplus.track.ws.beans.WSItemBean;
import com.trackplus.track.ws.tcl.TCLFacadeException;

class Updater {
    private static final Logger LOGGER = Logger.getLogger(Updater.class.getName());

    /**
     * Debug flag.
     */
    public static boolean debug = false;

    static boolean perform(AbstractBuild<?, ?> build, BuildListener listener)
            throws InterruptedException, IOException {
        PrintStream logger = listener.getLogger();
        List<TrackplusIssue> issues = null;
        AbstractProject p = build.getProject();
        try {
            TrackplusSite site = TrackplusSite.get(build.getProject());
            if (site == null) {
                logger.println(Messages.Updater_NoTrackplusSite());
                build.setResult(Result.FAILURE);
                return true;
            }

            String rootUrl = Hudson.getInstance().getRootUrl();

            if (rootUrl == null) {
                logger.println(Messages.Updater_NoHudsonUrl());
                build.setResult(Result.FAILURE);
                return true;
            }

            Set<Integer> ids = findIssueIdsRecursive(build, site.getIssuePrefix(), listener);

            if (ids.isEmpty()) {
                if (debug) {
                    logger.println("No Track+ issues found.");
                }
                return true; // nothing found here.
            }

            TrackplusSession session = null;
            try {
                session = site.createSession();
            } catch (/*Service*/Exception e) {
                listener.getLogger().println(Messages.Updater_FailedToConnect());
                e.printStackTrace(listener.getLogger());
            }
            if (session == null) {
                logger.println(Messages.Updater_NoRemoteAccess());
                build.setResult(Result.FAILURE);
                return true;
            }

            boolean doUpdate = false;
            if (site.updateTrackplusIssueForAllStatus) {
                doUpdate = true;
            } else {
                doUpdate = build.getResult().isBetterOrEqualTo(Result.UNSTABLE);
            }

            issues = getTrackplusIssues(ids, session, logger);

            build.getActions().add(new TrackplusBuildAction(build, issues));

            if (doUpdate) {
                submitComments(build, logger, rootUrl, issues, session, site.recordScmChanges);
            } else {
                // this build didn't work, so carry forward the issues to the next build
                build.addAction(new TrackplusCarryOverAction(issues));
            }
        } catch (Exception e) {
            logger.println("Error updating trackplus issues. Saving issues for next build.\n" + e);
            if (issues != null && !issues.isEmpty()) {
                // updating issues failed, so carry forward issues to the next build
                build.addAction(new TrackplusCarryOverAction(issues));
            }
        }

        return true;
    }

    /**
     * Submits comments for the given issues.
     * Removes from <code>issues</code> the ones which appear to be invalid.
     * @param build
     * @param logger
     * @param hudsonRootUrl
     * @param issues
     * @param session
     * @param recordScmChanges
     * @throws RemoteException
     */
    static void submitComments(AbstractBuild<?, ?> build, PrintStream logger, String hudsonRootUrl,
            List<TrackplusIssue> issues, TrackplusSession session, boolean recordScmChanges)
            throws RemoteException {
        // copy to prevent ConcurrentModificationException
        List<TrackplusIssue> copy = new ArrayList<TrackplusIssue>(issues);
        for (TrackplusIssue issue : copy) {
            try {
                logger.println(Messages.Updater_Updating(issue.getId()));
                StringBuilder aggregateComment = new StringBuilder();
                for (Entry e : build.getChangeSet()) {
                    if (e.getMsg().contains(Integer.toString(issue.getId()))) {
                        aggregateComment.append(e.getMsg()).append("<br>");
                    }
                }
                session.addComment(issue.getId(),
                        createComment(build, hudsonRootUrl, aggregateComment.toString(), recordScmChanges, issue));
            } catch (TCLFacadeException e) {
                String message = TrackplusSession.getMessage(e);
                logger.println("Can't add comment at " + issue.getId() + ".\n" + message);
                issues.remove(issue);
            }
        }
    }

    private static List<TrackplusIssue> getTrackplusIssues(Set<Integer> ids, TrackplusSession session,
            PrintStream logger) throws RemoteException {
        List<TrackplusIssue> issues = new ArrayList<TrackplusIssue>(ids.size());
        for (Integer id : ids) {
            try {
                WSItemBean wsItem = session.getIssue(Integer.toString(id));
                if (wsItem != null) {
                    issues.add(new TrackplusIssue(wsItem));
                }
            } catch (TCLFacadeException e) {
                String message = TrackplusSession.getMessage(e);
                LOGGER.log(Level.WARNING, "Error obtaining issue with id \"" + id + "\":" + message);
            }
        }
        return issues;
    }

    /**
     * Creates a comment to be used in Track+ for the build.
     */
    private static String createComment(AbstractBuild<?, ?> build, String hudsonRootUrl, String scmComments,
            boolean recordScmChanges, TrackplusIssue trackplusIssue) {
        String comment = String.format(
                Messages.Updater_BuildIntegrated() + " <b><a target='hudson' href='%4$s'>%2$s</a></b>.<br><br>"
                        + "<b>" + Messages.Updater_CommitComment() + "</b>:<br>%5$s",
                hudsonRootUrl, build, build.getResult().color.getImage(),
                Util.encode(hudsonRootUrl + build.getUrl()), scmComments);
        if (recordScmChanges) {
            List<String> scmChanges = getScmComments(build, trackplusIssue);
            StringBuilder sb = new StringBuilder(comment);
            for (String scmChange : scmChanges) {
                sb.append("<br>").append(scmChange);
            }
            return sb.toString();
        }
        return comment;
    }

    private static List<String> getScmComments(AbstractBuild<?, ?> build, TrackplusIssue trackplusIssue) {
        RepositoryBrowser repoBrowser = null;
        if (build.getProject().getScm() != null) {
            repoBrowser = build.getProject().getScm().getEffectiveBrowser();
        }
        List<String> scmChanges = new ArrayList<String>();
        for (Entry change : build.getChangeSet()) {
            if (trackplusIssue != null
                    && !StringUtils.contains(change.getMsg(), Integer.toString(trackplusIssue.getId()))) {
                continue;
            }
            try {
                String uid = change.getAuthor().getId();
                URL url = repoBrowser == null ? null : repoBrowser.getChangeSetLink(change);
                StringBuilder scmChange = new StringBuilder();
                if (StringUtils.isNotBlank(uid)) {
                    scmChange.append("<br><b>").append(Messages.Updater_CommittedBy()).append("</b>:<br>")
                            .append(uid).append(" <br> ");
                }
                if (url != null && StringUtils.isNotBlank(url.toExternalForm())) {
                    scmChange.append("<a target='vc' href='" + url.toExternalForm() + "'>" + url.toExternalForm()
                            + "</a><br>");
                }
                scmChange.append("<b>").append(Messages.Updater_AffectedFiles()).append("</b>: ").append("<br>");
                for (AffectedFile affectedFile : change.getAffectedFiles()) {
                    scmChange.append("* ").append(affectedFile.getPath()).append("<br>");
                }
                if (scmChange.length() > 0) {
                    scmChanges.add(scmChange.toString());
                }
            } catch (IOException e) {
                LOGGER.warning("skip failed to calculate scm repo browser link " + e.getMessage());
            }
        }
        return scmChanges;
    }

    private static String getRevision(Entry entry) {
        // svn at least can get the revision
        try {
            Class<?> clazz = entry.getClass();
            Method method = clazz.getMethod("getRevision", (Class[]) null);
            if (method == null) {
                return null;
            }
            Object revObj = method.invoke(entry, (Object[]) null);
            return (revObj != null) ? revObj.toString() : null;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Finds the strings that match Track+ issue ID patterns.
     */
    private static Set<Integer> findIssueIdsRecursive(AbstractBuild<?, ?> build, String issuePrefix,
            BuildListener listener) {
        Set<Integer> ids = new HashSet<Integer>();

        // first, issues that were carried forward.
        Run<?, ?> prev = build.getPreviousBuild();
        if (prev != null) {
            TrackplusCarryOverAction a = prev.getAction(TrackplusCarryOverAction.class);
            if (a != null) {
                ids.addAll(a.getIDs());
            }
        }

        // then issues in this build
        findIssues(build, ids, issuePrefix, listener);

        // check for issues fixed in dependencies
        for (DependencyChange depc : build.getDependencyChanges(build.getPreviousBuild()).values())
            for (AbstractBuild<?, ?> b : depc.getBuilds())
                findIssues(b, ids, issuePrefix, listener);

        return ids;
    }

    /**
     * @param pattern pattern to use to match issue ids
     */
    static void findIssues(AbstractBuild<?, ?> build, Set<Integer> ids, String issuePrefix,
            BuildListener listener) {
        for (Entry change : build.getChangeSet()) {
            LOGGER.fine("Looking for Track+ ID in " + change.getMsg());
            Set<Integer> tmpIds = TrackplusSite.getWorkItemIDs(change.getMsg(), issuePrefix);
            if (tmpIds != null && tmpIds.size() > 0) {
                for (Iterator iterator = tmpIds.iterator(); iterator.hasNext();) {
                    Integer id = (Integer) iterator.next();
                    ids.add(id);
                }
            } else {
                listener.getLogger().println("No issues found!");
            }
        }
    }
}