package org.hudson.trayapp.model; import java.io.IOException; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Vector; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.hudson.trayapp.gui.tray.TrayIconImplementation; import org.hudson.trayapp.model.job.Build; import org.hudson.trayapp.model.job.HealthReport; import org.hudson.trayapp.util.XMLHelper; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; public class Job { private static final long serialVersionUID = 1L; private String name; private String url; private Integer colour; private Build lastBuild; private Build lastFailedBuild; private Build lastSuccessfulBuild; private List healthReports = new Vector(); public final static Integer RED = new Integer(1); public final static Integer RED_ANIME = new Integer(2); public final static Integer YELLOW = new Integer(3); public final static Integer YELLOW_ANIME = new Integer(4); public final static Integer BLUE = new Integer(5); public final static Integer BLUE_ANIME = new Integer(6); public final static Integer GREY = new Integer(7); public final static Integer BUILD_CHANGED = new Integer(8); public final static Integer BUILD_UNCHANGED = new Integer(9); private final static Map COLOURSTOSTRING = new HashMap(); static { COLOURSTOSTRING.put(RED, "red"); COLOURSTOSTRING.put(RED_ANIME, "red_anime"); COLOURSTOSTRING.put(YELLOW, "yellow"); COLOURSTOSTRING.put(YELLOW_ANIME, "yellow_anime"); COLOURSTOSTRING.put(BLUE, "blue"); COLOURSTOSTRING.put(BLUE_ANIME, "blue_anime"); COLOURSTOSTRING.put(GREY, "grey"); } public static Job process(Node node) { Job job = new Job(); job.process(node.getChildNodes()); return job; } private void process(NodeList nodes) { for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); String name = node.getNodeName(); String value = XMLHelper.getTextContent(node); if (name.equals("name")) { this.name = value; } else if (name.equals("url")) { url = value; } else if (name.equals("color")) { if (value.equals("disabled")) { value = "grey"; } if (value.equals("aborted")) { value = "grey"; } if (value.equals("aborted_anime")) { value = "grey"; } colour = convertColour(value); } else if (name.equals("lastBuild")) { lastBuild = Build.process(node); } else if (name.equals("lastFailedBuild")) { lastFailedBuild = Build.process(node); } else if (name.equals("lastSuccessfulBuild")) { lastSuccessfulBuild = Build.process(node); } else if (name.equals("healthReport")) { healthReports.add(HealthReport.process(node)); } } } public void update() { try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); InputSource inputSource = new InputSource(url); Document document = builder.parse(inputSource); healthReports.clear(); process(document.getChildNodes()); } catch (ParserConfigurationException e) { TrayIconImplementation.displayException("Job Update Exception", "Updating Job " + name, e); } catch (IOException e) { TrayIconImplementation.displayException("Job Update Exception", "Updating Job " + name, e); } catch (SAXException e) { TrayIconImplementation.displayException("Job Update Exception", "Updating Job " + name, e); } } public String getName() { return name; } public String getUrl() { return url; } /** * Note this method is far from complete. Right now it encapsulates " "'s with %20's and that's it. * @return */ public String getRFC2396CompliantURL() { return getRFC2396CompliantURL(url); } public static String getRFC2396CompliantURL(String url) { return url.replaceAll(" ", "%20"); } public String getColour() { return getColour(colour); } public static String getColour(Integer colour) { return (String) COLOURSTOSTRING.get(colour); } public Object clone() { Job jobReturn = new Job(); jobReturn.colour = colour; jobReturn.name = name; jobReturn.url = url; if (lastBuild != null) jobReturn.lastBuild = (Build) lastBuild.clone(); if (lastFailedBuild != null) jobReturn.lastFailedBuild = (Build) lastFailedBuild.clone(); if (lastSuccessfulBuild != null) jobReturn.lastSuccessfulBuild = (Build) lastSuccessfulBuild.clone(); Iterator iterReports = healthReports.iterator(); while (iterReports.hasNext()) { jobReturn.healthReports.add(((HealthReport) iterReports.next()).clone()); } return jobReturn; } public int hashCode() { return url.hashCode(); } /** * Note this will only test equality based upon the URL. If you want to test complete equality, you must call equalsState() * @return */ public boolean equals(Object o) { if (o instanceof Job) { Job job = (Job) o; return url.equals(job.url); } return false; } // /** // * Unlike the standard equality test, which in this classes implementation simmply checks upon the URL matching, this // * method will do a full compare. This was chosen so that jobs can be updated in lists simply, but tested against // * each other with relative ease. // * @param job // * @return // */ // public boolean equals(Job job) { // if (job == null) return false; // return name.equals(job.name) && colour.equals(job.colour) && url.equals(job.url); // } /** * This method will take the colour given as a string, and return the underlying Integer representing * it. * @param colour The colour given as a String, as Hudson would report it * @return Either the Integer for the colour, or null if it couldn't be matched. */ public static Integer convertColour(String colour) { Iterator iterator = COLOURSTOSTRING.entrySet().iterator(); Integer integer = null; while (integer == null && iterator.hasNext()) { Map.Entry entry = (Map.Entry) iterator.next(); if (entry.getValue().equals(colour)) { integer = (Integer) entry.getKey(); } } return integer; } /** * This method will traverse all of the health reports for this job, and return the worst scoring job. If more than * one healthreport provides the same score, then the list is populated with the multiple health reports suffering * with this score. Note that in order to get the worst score. Note, by design, this method will not return 100% * HealthReports, as this would cause a significant problem in rendering later on. * @return This method will return a list that will either contain no HealthReports, indicating that the job is * 100% healthy, one or many HealthReports, indicating that this job has a worst health score shared by one or * many health reports, or will return null, indicating that this job does not support Health reporting. Typically * this is for Hudson servers prior to build 173. */ public List getWorstHealth() { // The first thing we do is confirm that we even have any reports. If not return null. if (healthReports.size() == 0) { return null; } List list = new Vector(); int score = 100; Iterator iterator = healthReports.iterator(); while (iterator.hasNext()) { HealthReport report = (HealthReport) iterator.next(); /* * First we check to see if the report is equal to our worst case. If so, add the health report to the * returning list. */ if (report.getScore() == score && report.getScore() != 100) { list.add(report); } else if (report.getScore() < score) { /* * If we have a report score that is lower than what we already have, then we need to clear out * the returning list, add this report to the now empty list, and set the score lower */ score = report.getScore(); list.clear(); list.add(report); } } return list; } /** * This method will look for the worst health of the job's health reports. Note this method uses the method * getWorstHealth() to identify the HealthReports that are suffering, and then interprets the results as explained * in that methods JavaDoc. * @return */ public int getWorstHealthScore() { List list = getWorstHealth(); if (list == null) { return -1; } if (list.isEmpty()) { return 100; } // We know that the List must contain something, thus we can confidently call this final method. return ((HealthReport) list.get(0)).getScore(); } /** * @return the lastBuild */ public Build getLastBuild() { return lastBuild; } /** * @return the lastFailedBuild */ public Build getLastFailedBuild() { return lastFailedBuild; } /** * @return the lastSuccessfulBuild */ public Build getLastSuccessfulBuild() { return lastSuccessfulBuild; } }