package io.trygvis.esper.testing.jenkins; import fj.*; import fj.data.*; import io.trygvis.esper.testing.*; import static io.trygvis.esper.testing.XmlUtil.*; import io.trygvis.esper.testing.jenkins.JenkinsJobXml.*; import static java.lang.Integer.*; import static org.apache.commons.lang.StringUtils.*; import org.codehaus.httpcache4j.*; import org.codehaus.httpcache4j.cache.*; import org.h2.util.*; import org.jdom2.*; import org.jdom2.input.*; import javax.xml.stream.*; import java.io.*; import java.net.*; import java.util.*; import java.util.List; public class JenkinsClient { private static final XMLInputFactory xmlReader = XMLInputFactory.newFactory(); private static final StAXStreamBuilder streamBuilder = new StAXStreamBuilder(); private final HTTPCache http; private boolean debugXml; public JenkinsClient(HTTPCache http) { this.http = http; this.debugXml = false; } public void setDebugXml(boolean debugXml) { this.debugXml = debugXml; } public JenkinsXml fetchJobs(URI uri) throws XMLStreamException, JDOMException, IOException { Element root = fetchXml(uri).getRootElement(); List jobs = new ArrayList<>(); for (Element job : root.getChildren("job")) { String name = trimToNull(job.getChildText("name")); String url = trimToNull(job.getChildText("url")); String color = trimToNull(job.getChildText("color")); if (name == null || url == null || color == null) { continue; } jobs.add(new JenkinsJobEntryXml(name, url, color)); } return new JenkinsXml( Option.fromNull(root.getChildText("nodeName")), Option.fromNull(root.getChildText("nodeDescription")), Option.fromNull(root.getChildText("description")), jobs); } public JenkinsJobXml fetchJob(URI uri) throws IOException, JDOMException, XMLStreamException { Element root = fetchXml(uri).getRootElement(); String name = root.getName(); switch (name) { case "freeStyleProject": return JenkinsJobXml.parse(uri, JenkinsJobType.FREE_STYLE, root); case "mavenModuleSet": return JenkinsJobXml.parse(uri, JenkinsJobType.MAVEN, root); default: throw new IOException("Unknown project type: " + name); } } private Document fetchXml(URI uri) throws IOException, XMLStreamException, JDOMException { HTTPResponse response = null; try { response = http.execute(new HTTPRequest(uri)); if (response.getStatus().getCode() != 200) { throw new IOException("Did not get 200 back, got " + response.getStatus().getCode()); } InputStream stream = response.getPayload().getInputStream(); if (debugXml) { int size; try { size = parseInt(response.getHeaders().getFirstHeader("Content-Length").getValue()); } catch (Throwable e) { size = 10 * 1024; } // TODO: Pretty print ByteArrayOutputStream buffer = new ByteArrayOutputStream(size); IOUtils.copy(stream, buffer); byte[] bytes = buffer.toByteArray(); System.out.println("------------------------------------------------"); System.out.write(bytes); System.out.println(); System.out.println("------------------------------------------------"); stream = new ByteArrayInputStream(bytes); } return streamBuilder.build(xmlReader.createXMLStreamReader(stream)); } catch (HTTPException e) { throw new IOException(e); } finally { if (response != null) { response.consume(); } } } } 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 } 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(XmlUtil.parseInt); Option url = childText(element, "url").bind(parseUri); if(number.isNone() || url.isNone()) { return Option.none(); } return Option.some(new BuildXml(number.some(), url.some())); } }; BuildXml(int number, URI url) { this.number = number; this.url = url; } } public static JenkinsJobXml parse(URI uri, JenkinsJobType type, Element root) { return new JenkinsJobXml(type, childText(root, "description"), childText(root, "displayName"), childText(root, "name"), childText(root, "url").bind(parseUri).orSome(uri), childText(root, "color"), childText(root, "buildable").bind(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)); } }