package io.trygvis.esper.testing.jenkins; import fj.*; import fj.data.*; import io.trygvis.esper.testing.*; import io.trygvis.esper.testing.jenkins.JenkinsJobXml.*; import io.trygvis.esper.testing.util.*; import org.apache.abdera.*; import org.apache.abdera.model.*; import org.apache.abdera.parser.*; import org.codehaus.httpcache4j.cache.*; import org.jdom2.Document; import org.jdom2.Element; import org.joda.time.DateTime; import org.slf4j.*; import java.io.*; import java.net.*; import java.util.*; import java.util.List; import static fj.data.Option.*; import static io.trygvis.esper.testing.Util.*; import static io.trygvis.esper.testing.util.HttpClient.inputStreamOnly; import static org.apache.commons.lang.StringUtils.*; public class JenkinsClient { private static final Logger logger = LoggerFactory.getLogger(JenkinsClient.class); private final XmlHttpClient xmlHttpClient; private final HttpClient> jenkinsEntryXmlClient; private final Parser parser; public JenkinsClient(HTTPCache http, Abdera abdera) { this.xmlHttpClient = new XmlHttpClient(http); this.parser = abdera.getParser(); jenkinsEntryXmlClient = new HttpClient<>(http, inputStreamOnly(new F>>() { public Option> f(InputStream inputStream) { Feed feed = (Feed) parser.parse(inputStream).getRoot(); List list = new ArrayList<>(); for (Entry entry : feed.getEntries()) { try { list.add(new JenkinsEntryXml(entry.getIdElement().getText(), new DateTime(entry.getPublished().getTime()), entry.getAlternateLinkResolvedHref().toURI())); } catch (URISyntaxException ignore) { } } return some(list); } })); } public static URI apiXml(URI url) { String u = url.toASCIIString(); if(u.endsWith("/")) { u = u.substring(0, u.length() - 1); } return URI.create(u + "/api/xml"); } public Option> fetchRss(URI url) { return jenkinsEntryXmlClient.fetch(url); } public JenkinsXml fetchJobs(URI url) { Option d = xmlHttpClient.fetch(url); if (d.isNone()) { Option n = Option.none(); return new JenkinsXml(n, n, n, Collections.emptyList()); } Element root = d.some().getRootElement(); List jobs = new ArrayList<>(); for (Element job : root.getChildren("job")) { String name = trimToNull(job.getChildText("name")); String u = trimToNull(job.getChildText("url")); String color = trimToNull(job.getChildText("color")); if (name == null || u == null || color == null) { continue; } jobs.add(new JenkinsJobEntryXml(name, u, color)); } return new JenkinsXml( Option.fromNull(root.getChildText("nodeName")), Option.fromNull(root.getChildText("nodeDescription")), Option.fromNull(root.getChildText("description")), jobs); } public Option fetchJob(URI url) { Option d = xmlHttpClient.fetch(url); if (d.isNone()) { return Option.none(); } Element root = d.some().getRootElement(); String name = root.getName(); switch (name) { case "freeStyleProject": return some(JenkinsJobXml.parse(url, JenkinsJobType.FREE_STYLE, root)); case "mavenModuleSet": return some(JenkinsJobXml.parse(url, JenkinsJobType.MAVEN, root)); case "matrixProject": return some(JenkinsJobXml.parse(url, JenkinsJobType.MATRIX, root)); default: logger.warn("Unknown project type: " + name); return Option.none(); } } public Option fetchBuild(URI url) { Option d = xmlHttpClient.fetch(url); if (d.isNone()) { return Option.none(); } Element root = d.some().getRootElement(); String name = root.getName(); switch (name) { case "matrixBuild": case "matrixRun": case "mavenModuleSetBuild": case "freeStyleBuild": return JenkinsBuildXml.parse(root); default: logger.warn("Unknown build type: " + name); return Option.none(); } } public static class JenkinsBuildXml { public final URI url; public final int number; public final String result; public final int duration; public final long timestamp; JenkinsBuildXml(URI url, int number, String result, int duration, long timestamp) { this.url = url; this.number = number; this.result = result; this.duration = duration; this.timestamp = timestamp; } public static Option parse(Element root) { Option url = childText(root, "url").bind(Util.parseUri); Option number = childText(root, "number").bind(Util.parseInt); Option result = childText(root, "result"); Option duration = childText(root, "duration").bind(Util.parseInt); Option timestamp = childText(root, "timestamp").bind(Util.parseLong); if(url.isNone() || number.isNone() || result.isNone() || duration.isNone() || timestamp.isNone()) { logger.warn("Missing required fields."); return none(); } return some(new JenkinsBuildXml(url.some(), number.some(), result.some(), duration.some(), timestamp.some())); } }} class JenkinsEntryXml { public final String id; public final DateTime timestamp; public final URI url; JenkinsEntryXml(String id, DateTime timestamp, URI url) { this.id = id; this.timestamp = timestamp; this.url = url; } } class JenkinsXml { public final Option nodeName; public final Option nodeDescription; public final Option description; public final List jobs; JenkinsXml(Option nodeName, Option nodeDescription, Option description, List jobs) { this.nodeName = nodeName; this.nodeDescription = nodeDescription; this.description = description; this.jobs = jobs; } } class JenkinsJobEntryXml { public final String name; public final String url; public final String color; JenkinsJobEntryXml(String name, String url, String color) { this.name = name; this.url = url; this.color = color; } } class JenkinsJobXml { enum JenkinsJobType { MAVEN, FREE_STYLE, MATRIX } public final JenkinsJobType type; public final Option description; public final Option displayName; public final Option name; public final URI url; public final Option color; public final boolean buildable; public final Option lastBuild; public final Option lastCompletedBuild; public final Option lastFailedBuild; public final Option lastSuccessfulBuild; public final Option lastUnsuccessfulBuild; protected JenkinsJobXml(JenkinsJobType type, Option description, Option displayName, Option name, URI url, Option color, boolean buildable, Option lastBuild, Option lastCompletedBuild, Option lastFailedBuild, Option lastSuccessfulBuild, Option lastUnsuccessfulBuild) { this.type = type; this.description = description; this.displayName = displayName; this.name = name; this.url = url; this.color = color; this.buildable = buildable; this.lastBuild = lastBuild; this.lastCompletedBuild = lastCompletedBuild; this.lastFailedBuild = lastFailedBuild; this.lastSuccessfulBuild = lastSuccessfulBuild; this.lastUnsuccessfulBuild = lastUnsuccessfulBuild; } static class BuildXml { public final int number; public final URI url; public static F> buildXml = new F>() { public Option f(Element element) { Option number = childText(element, "number").bind(Util.parseInt); Option url = childText(element, "url").bind(Util.parseUri); if (number.isNone() || url.isNone()) { return Option.none(); } return some(new BuildXml(number.some(), url.some())); } }; BuildXml(int number, URI url) { this.number = number; this.url = url; } } public static JenkinsJobXml parse(URI url, JenkinsJobType type, Element root) { return new JenkinsJobXml(type, childText(root, "description"), childText(root, "displayName"), childText(root, "name"), childText(root, "url").bind(Util.parseUri).orSome(url), childText(root, "color"), childText(root, "buildable").bind(Util.parseBoolean).orSome(false), child(root, "lastBuild").bind(BuildXml.buildXml), child(root, "lastCompletedBuild").bind(BuildXml.buildXml), child(root, "lastFailedBuild").bind(BuildXml.buildXml), child(root, "lastSuccessfulBuild").bind(BuildXml.buildXml), child(root, "lastUnsuccessfulBuild").bind(BuildXml.buildXml)); } }