package org.jvnet.hudson.plugins.jira.issueversioning.plugin.hudson.rest; import javax.ws.rs.core.MediaType; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import java.io.IOException; import java.io.PrintStream; import java.io.StringWriter; import java.net.MalformedURLException; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import hudson.model.AbstractBuild; import hudson.model.Result; import hudson.model.Run.Artifact; import hudson.plugins.jira.JiraBuildAction; import hudson.plugins.jira.JiraCarryOverAction; import hudson.plugins.jira.JiraIssue; import hudson.scm.ChangeLogSet.Entry; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.StringRequestEntity; import org.jvnet.hudson.plugins.jira.issueversioning.domain.api.model.rest.Build; import org.jvnet.hudson.plugins.jira.issueversioning.domain.api.model.rest.Project; import org.jvnet.hudson.plugins.jira.issueversioning.plugin.hudson.utils.JiraKeyUtils; import org.jvnet.hudson.plugins.jira.issueversioning.plugin.hudson.utils.ProjectUtils; /** * Posts issues in the current build and any previous failed builds to the specified Jira url. * * @author Stig Kleppe-Jorgensen, 2010.01.05 */ public class IssuesToJiraPoster { private static final Pattern pattern = Pattern.compile(".*-([0-9.]+).jar$"); private final AbstractBuild build; private String baseUri; private final PrintStream logger; public IssuesToJiraPoster(AbstractBuild build, String baseUri, PrintStream logger) throws MalformedURLException { this.build = build; this.baseUri = baseUri; this.logger = logger; } /** * Posts the issues wrapped inside its project and its builds to Jira. * * @return true if the URL to post to is ok, false otherwise. */ public boolean post() throws JAXBException, IOException { Project project = createProject(build); final String marshaledProject = marshal(project); int status = postToJira(marshaledProject); final boolean postOk = status < 300; if (postOk) { logger.println("Posted the following " + numIssues(project) + " issues to Jira that is added to version " + project.getVersionForOkBuild() + " if status is fixed:"); logger.println(project.getAllIssues()); } else { logger.println("ERROR: posting to " + baseUri + " failed. Make sure the Jira URL is setup correctly"); } return postOk; } private Project createProject(AbstractBuild<?, ?> build) { final String version = lookupVersionFromArtifacts(build.getArtifacts()); final Project project = new Project(build.getProject().getName(), version, createBuild(build)); AbstractBuild<?, ?> previousBuild = build.getPreviousBuild(); while (hasFailedOrNotNull(previousBuild)) { project.addFailedBuild(createBuild(previousBuild)); previousBuild = previousBuild.getPreviousBuild(); } return project; } private String lookupVersionFromArtifacts(List<? extends Artifact> artifacts) { for (Artifact artifact : artifacts) { final Matcher matcher = pattern.matcher(artifact.getFileName()); if (matcher.matches()) { return matcher.group(1); } } throw new IllegalStateException("Could not find version information in generated artifacts. " + "Does this build produce Maven type artifacts?"); } private Build createBuild(AbstractBuild<?, ?> build) { return new Build(build.getNumber(), findCurrentBuildIssues(build)); } private Set<String> findCurrentBuildIssues(AbstractBuild<?, ?> build) { Set<String> issueKeys = new HashSet<String>(); final JiraBuildAction jiraBuildAction = build.getAction(JiraBuildAction.class); final JiraIssue[] issues = jiraBuildAction.issues; final JiraCarryOverAction jiraCarryOverAction = build.getAction(JiraCarryOverAction.class); final Collection<String> issueIds = jiraCarryOverAction.getIDs(); if (ProjectUtils.getJiraProjectKeyPropertyOfProject(build.getProject()) != null) { Pattern pattern = ProjectUtils.getJiraProjectKeyPropertyOfProject(build.getProject()).getIssueKeyPattern(); for (Entry entry : build.getChangeSet()) { issueKeys.addAll(JiraKeyUtils.getJiraIssueKeysFromText(entry.getMsg(), pattern)); } } return issueKeys; } private Set<String> findCarryOverIssues(AbstractBuild<?, ?> build) { Set<String> issueKeys = new HashSet<String>(); final JiraBuildAction jiraBuildAction = build.getAction(JiraBuildAction.class); final JiraIssue[] issues = jiraBuildAction.issues; final JiraCarryOverAction jiraCarryOverAction = build.getAction(JiraCarryOverAction.class); final Collection<String> issueIds = jiraCarryOverAction.getIDs(); if (ProjectUtils.getJiraProjectKeyPropertyOfProject(build.getProject()) != null) { Pattern pattern = ProjectUtils.getJiraProjectKeyPropertyOfProject(build.getProject()).getIssueKeyPattern(); for (Entry entry : build.getChangeSet()) { issueKeys.addAll(JiraKeyUtils.getJiraIssueKeysFromText(entry.getMsg(), pattern)); } } return issueKeys; } private boolean hasFailedOrNotNull(AbstractBuild<?, ?> previousBuild) { if (previousBuild == null) { return false; } final Result result = previousBuild.getResult(); return result.isWorseOrEqualTo(Result.FAILURE); } private String marshal(Project project) throws JAXBException { JAXBContext jc = JAXBContext.newInstance(new Class<?>[]{Project.class, Build.class}); final Marshaller marshaller = jc.createMarshaller(); final StringWriter writer = new StringWriter(); marshaller.marshal(project, writer); return writer.toString(); } private int postToJira(String marshaledProject) throws IOException { HttpClient client = new HttpClient(); PostMethod post = new PostMethod(baseUri); post.setRequestEntity(new StringRequestEntity(marshaledProject, MediaType.APPLICATION_XML, null)); return client.executeMethod(post); } private int numIssues(Project project) { int numIssues = project.getOkBuild().getIssues().size(); for (Build failedBuild : project.getFailedBuilds()) { numIssues += failedBuild.getIssues().size(); } return numIssues; } }