/* * Copyright (c) 2007-2009 Yahoo! Inc. All rights reserved. * The copyrights to the contents of this file are licensed under the MIT License (http://www.opensource.org/licenses/mit-license.php) */ package hudson.plugins.plot; import hudson.Extension; import hudson.FilePath; import hudson.Launcher; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.Action; import hudson.model.Build; import hudson.model.BuildListener; import hudson.model.Project; import hudson.tasks.BuildStepDescriptor; import hudson.tasks.BuildStepMonitor; import hudson.tasks.Publisher; import hudson.tasks.Recorder; import hudson.util.FormValidation; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Random; import java.util.logging.Level; import java.util.logging.Logger; import javax.servlet.ServletException; import net.sf.json.JSONArray; import net.sf.json.JSONObject; import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; /** * Records the plot data for builds. * * @author Nigel Daley */ public class PlotPublisher extends Recorder { private static final Logger LOGGER = Logger.getLogger(PlotPublisher.class.getName()); /** * Array of Plot objects that represent the job's configured * plots; must be non-null */ private ArrayList<Plot> plots = new ArrayList<Plot>(); /** * Maps plot groups to plot objects; group strings are in a * URL friendly format; map must be non-null */ transient private HashMap<String,ArrayList<Plot>> groupMap = new HashMap<String,ArrayList<Plot>>(); /** * Setup the groupMap upon deserialization. */ private Object readResolve() { Plot[] p = plots.toArray(new Plot[] {}); setPlots(p); return this; } /** * Converts a URL friendly plot group name to the original group name. * If the given urlGroup doesn't already exist then the empty string will * be returned. */ public String urlGroupToOriginalGroup(String urlGroup) { if (urlGroup == null || "nogroup".equals(urlGroup)) { return "Plots"; } if (groupMap.containsKey(urlGroup)) { ArrayList<Plot> plots = groupMap.get(urlGroup); if (plots.size() > 0) { return plots.get(0).group; } } return ""; } /** * Converts the original plot group name to a URL friendly group name. */ public String originalGroupToUrlGroup(String originalGroup) { if (originalGroup == null || "".equals(originalGroup)) { return "nogroup"; } try { return URLEncoder.encode(originalGroup.replace('/',' '),"UTF-8"); } catch (UnsupportedEncodingException uee) { // should never happen return originalGroup; } } /** * Returns all group names as the original user specified strings. */ public String[] getOriginalGroups() { ArrayList<String> originalGroups = new ArrayList<String>(); for (String urlGroup : groupMap.keySet()) { originalGroups.add(urlGroupToOriginalGroup(urlGroup)); } String[] retVal = originalGroups.toArray(new String[] {}); Arrays.sort(retVal); return retVal; } /** * Replaces the plots managed by this object with the given list. * * @param plots the new list of plots */ public void setPlots(Plot[] plots) { this.plots = new ArrayList<Plot>(); groupMap = new HashMap<String,ArrayList<Plot>>(); for (Plot plot : plots) { addPlot(plot); } } /** * Adds the new plot to the plot data structures managed by this object. * * @param plot the new plot */ public void addPlot(Plot plot) { // update the plot list plots.add(plot); // update the group-to-plot map String urlGroup = originalGroupToUrlGroup(plot.getGroup()); if (groupMap.containsKey(urlGroup)) { ArrayList<Plot> list = groupMap.get(urlGroup); list.add(plot); } else { ArrayList<Plot> list = new ArrayList<Plot>(); list.add(plot); groupMap.put(urlGroup, list); } } /** * Returns the entire list of plots managed by this object. */ public Plot[] getPlots() { return plots.toArray(new Plot[] {}); } /** * Returns the list of plots with the given group name. The given * group must be the URL friendly form of the group name. */ public Plot[] getPlots(String urlGroup) { ArrayList<Plot> p = groupMap.get(urlGroup); if (p != null) { return p.toArray(new Plot[] {}); } return new Plot[] {}; } /** * Called by Hudson. */ @Override public Action getProjectAction(AbstractProject<?,?> project) { return project instanceof Project ? new PlotAction((Project)project, this) : null; } public BuildStepMonitor getRequiredMonitorService() { return BuildStepMonitor.BUILD; } /** * Called by Hudson. */ @Override public BuildStepDescriptor<Publisher> getDescriptor() { return DESCRIPTOR; } /** * Called by Hudson when a build is finishing. */ @Override public boolean perform(AbstractBuild<?,?> build, Launcher launcher, BuildListener listener) throws IOException, InterruptedException { // Should always be a Build due to isApplicable below if (!(build instanceof Build)) return true; listener.getLogger().println("Recording plot data"); // add the build to each plot for (Plot plot : getPlots()) { plot.addBuild((Build)build,listener.getLogger()); } // misconfigured plots will not fail a build so always return true return true; } @Extension public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl(); public static class DescriptorImpl extends BuildStepDescriptor<Publisher> { public DescriptorImpl() { super(PlotPublisher.class); } public String getDisplayName() { return "Plot build data"; } @Override public boolean isApplicable(Class<? extends AbstractProject> jobType) { return Project.class.isAssignableFrom(jobType); } /** * Called when the user saves the project configuration. */ @Override public Publisher newInstance(StaplerRequest req, JSONObject formData) throws FormException { PlotPublisher publisher = new PlotPublisher(); for (Object data : SeriesFactory.getArray(formData.get("plots"))) { publisher.addPlot(bindPlot((JSONObject)data, req)); } return publisher; } private static Plot bindPlot(JSONObject data, StaplerRequest req) { Plot p = req.bindJSON(Plot.class, data); p.series = SeriesFactory.createSeriesList(data.get("series"), req); return p; } /** * Checks if the series file is valid. */ public FormValidation doCheckSeriesFile(@AncestorInPath AbstractProject project, @QueryParameter String value) throws IOException { return FilePath.validateFileMask(project.getSomeWorkspace(),value); } } }