package hudson.plugins.jira.selector; import java.util.Collection; import java.util.HashSet; 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 javax.annotation.Nonnull; import org.apache.commons.lang.StringUtils; import org.kohsuke.stapler.DataBoundConstructor; import hudson.Extension; import hudson.model.AbstractBuild; import hudson.model.Descriptor; import hudson.model.ParameterValue; import hudson.model.ParametersAction; import hudson.model.Run; import hudson.model.TaskListener; import hudson.model.AbstractBuild.DependencyChange; import hudson.plugins.jira.JiraCarryOverAction; import hudson.plugins.jira.JiraSite; import hudson.plugins.jira.Messages; import hudson.plugins.jira.RunScmChangeExtractor; import hudson.plugins.jira.listissuesparameter.JiraIssueParameterValue; import hudson.scm.ChangeLogSet; import hudson.scm.ChangeLogSet.Entry; public class DefaultIssueSelector extends AbstractIssueSelector { private static final Logger LOGGER = Logger.getLogger(DefaultIssueSelector.class.getName()); @DataBoundConstructor public DefaultIssueSelector() { } /** * See {@link #addIssuesRecursive(Run, JiraSite, TaskListener, Set)} */ @Override public Set<String> findIssueIds(@Nonnull final Run<?, ?> run, @Nonnull final JiraSite site, @Nonnull final TaskListener listener) { HashSet<String> issuesIds = new LinkedHashSet<String>(); addIssuesRecursive(run, site, listener, issuesIds); return issuesIds; } @Extension public static final class DescriptorImpl extends Descriptor<AbstractIssueSelector> { @Override public String getDisplayName() { return Messages.DefaultIssueSelector_DisplayName(); } } protected Logger getLogger() { return LOGGER; } /** * Finds the strings that match JIRA issue ID patterns. This method returns * all likely candidates and doesn't check if such ID actually exists or * not. We don't want to use {@link JiraSite#existsIssue(String)} here so * that new projects in JIRA can be detected. * */ protected static void findIssues(Run<?, ?> build, Set<String> issueIds, Pattern pattern, TaskListener listener) { for (ChangeLogSet<? extends Entry> set : RunScmChangeExtractor.getChanges(build)) { for (Entry change : set) { LOGGER.fine("Looking for JIRA ID in " + change.getMsg()); Matcher m = pattern.matcher(change.getMsg()); while (m.find()) { if (m.groupCount() >= 1) { String content = StringUtils.upperCase(m.group(1)); issueIds.add(content); } else { listener.getLogger() .println("Warning: The JIRA pattern " + pattern + " doesn't define a capturing group!"); } } } } } /** * Calls {@link #findIssues(Run, Set, Pattern, TaskListener)} with * {@link JiraSite#getIssuePattern()} as pattern */ protected void addIssuesFromChangeLog(Run<?, ?> build, JiraSite site, TaskListener listener, Set<String> issueIds) { Pattern pattern = site.getIssuePattern(); findIssues(build, issueIds, pattern, listener); } /** * Adds issues to issueIds. Adds issues carried over from previous build, * issues from current build and from dependent builds * {@link #addIssuesCarriedOverFromPreviousBuild(Run, JiraSite, TaskListener, Set)} * {@link #addIssuesFromCurrentBuild(Run, JiraSite, TaskListener, Set)} * {@link #addIssuesFromDependentBuilds(Run, JiraSite, TaskListener, Set)} */ protected void addIssuesRecursive(Run<?, ?> build, JiraSite site, TaskListener listener, Set<String> issuesIds) { addIssuesCarriedOverFromPreviousBuild(build, site, listener, issuesIds); addIssuesFromCurrentBuild(build, site, listener, issuesIds); addIssuesFromDependentBuilds(build, site, listener, issuesIds); } /** * Adds issues to issueIds from the current build. Issues from parameters * are added as well as issues matching pattern * {@link #addIssuesFromChangeLog(Run, Pattern, TaskListener, Set)} * {@link #addIssuesFromParameters(Run, JiraSite, TaskListener, Set)} */ protected void addIssuesFromCurrentBuild(Run<?, ?> build, JiraSite site, TaskListener listener, Set<String> issueIds) { addIssuesFromChangeLog(build, site, listener, issueIds); addIssuesFromParameters(build, site, listener, issueIds); } /** * Adds issues to issueIds by examining dependency changes from last build. * For each dependency change * {@link #addIssuesRecursive(Run, JiraSite, TaskListener, Set) is called. */ protected void addIssuesFromDependentBuilds(Run<?, ?> build, JiraSite site, TaskListener listener, Set<String> issueIds) { for (DependencyChange depc : RunScmChangeExtractor.getDependencyChanges(build).values()) { for (AbstractBuild<?, ?> b : depc.getBuilds()) { getLogger().finer("Searching for JIRA issues in dependency " + b + " of " + build); addIssuesRecursive(b, site, listener, issueIds); } } } /** * Adds issues to issueIds from parameters */ protected void addIssuesFromParameters(Run<?, ?> build, JiraSite site, TaskListener listener, Set<String> issueIds) { // Now look for any JiraIssueParameterValue's set in the build // Implements JENKINS-12312 ParametersAction parameters = build.getAction(ParametersAction.class); if (parameters != null) { for (ParameterValue val : parameters.getParameters()) { if (val instanceof JiraIssueParameterValue) { String issueId = ((JiraIssueParameterValue) val).getValue().toString(); if (issueIds.add(issueId)) { getLogger().finer("Added perforce issue " + issueId + " from build " + build); } } } } } /** * Adds issues that were carried over from previous build to issueIds */ protected void addIssuesCarriedOverFromPreviousBuild(Run<?, ?> build, JiraSite site, TaskListener listener, Set<String> ids) { Run<?, ?> prev = build.getPreviousBuild(); if (prev != null) { JiraCarryOverAction a = prev.getAction(JiraCarryOverAction.class); if (a != null) { getLogger().finer("Searching for JIRA issues in previously failed build " + prev.number); Collection<String> jobIDs = a.getIDs(); ids.addAll(jobIDs); if (getLogger().isLoggable(Level.FINER)) { for (String jobId : a.getIDs()) { getLogger().finer("Adding job " + jobId); } } } } } }