Java tutorial
/* * 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!"); } } } }