package hudson.plugins.jira; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.LinkedHashSet; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import hudson.plugins.jira.model.JiraIssue; import org.apache.commons.lang.StringUtils; import hudson.Extension; import hudson.MarkupText; import hudson.Util; import hudson.model.Job; import hudson.model.Run; import hudson.scm.ChangeLogAnnotator; import hudson.scm.ChangeLogSet.Entry; /** * {@link ChangeLogAnnotator} that picks up JIRA issue IDs. * * @author Kohsuke Kawaguchi */ @Extension public class JiraChangeLogAnnotator extends ChangeLogAnnotator { private static final Logger LOGGER = Logger.getLogger(JiraChangeLogAnnotator.class.getName()); public JiraChangeLogAnnotator() { super(); LOGGER.fine("JiraChangeLogAnnotator created"); } @Override public void annotate(Run<?, ?> build, Entry change, MarkupText text) { JiraSite site = getSiteForProject(build.getParent()); if (site == null) { LOGGER.fine("not configured with JIRA"); return; // not configured with JIRA } LOGGER.log(Level.FINE, "Using site: {0}", site.getUrl()); // if there's any recorded detail information, try to use that, too. JiraBuildAction a = build.getAction(JiraBuildAction.class); Set<JiraIssue> issuesToBeSaved = new LinkedHashSet<>(); Pattern pattern = site.getIssuePattern(); if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("Using issue pattern: " + pattern); } String plainText = text.getText(); Matcher m = pattern.matcher(plainText); while (m.find()) { if (m.groupCount() >= 1) { String id = m.group(1); if (StringUtils.isNotBlank(site.userName) && !site.existsIssue(id)) { LOGGER.log(Level.INFO, "No known JIRA project corresponding to id: ''{0}''", id); continue; } LOGGER.log(Level.INFO, "Annotating JIRA id: ''{0}''", id); URL url, alternativeUrl; try { url = site.getUrl(id); } catch (MalformedURLException e) { throw new AssertionError(e); // impossible } try { alternativeUrl = site.getAlternativeUrl(id); if (alternativeUrl != null) { url = alternativeUrl; } } catch (MalformedURLException e) { LOGGER.log(Level.WARNING, "Failed to construct alternative URL for Jira link. " + e.getMessage()); // This should not fail, since we already have an URL object. Exceptions would happen elsewhere. throw new AssertionError(e); } JiraIssue issue = null; if (a != null) { issue = a.getIssue(id); } if (issue == null) { try { issue = site.getIssue(id); if (issue != null) { issuesToBeSaved.add(issue); } } catch (Exception e) { LOGGER.log(Level.FINE, "Error getting remote issue " + id, e); } } if (issue == null) { text.addMarkup(m.start(1), m.end(1), "<a href='" + url + "'>", "</a>"); } else { text.addMarkup(m.start(1), m.end(1), String.format("<a href='%s' tooltip='%s'>", url, Util.escape(issue.getSummary())), "</a>"); } } else { LOGGER.log(Level.WARNING, "The JIRA pattern " + pattern + " doesn't define a capturing group!"); } } if (!issuesToBeSaved.isEmpty()) { saveIssues(build, a, issuesToBeSaved); } } private void saveIssues(Run<?, ?> build, JiraBuildAction a, Set<JiraIssue> issuesToBeSaved) { if (a != null) { a.addIssues(issuesToBeSaved); } else { JiraBuildAction action = new JiraBuildAction(build, issuesToBeSaved); build.addAction(action); } try { build.save(); } catch (final IOException e) { LOGGER.log(Level.WARNING, "Error saving updated build", e); } } JiraSite getSiteForProject(Job<?, ?> project) { return JiraSite.get(project); } }