package hudson.plugins.analysis.graph; import javax.servlet.ServletException; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.lang3.StringUtils; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import com.google.common.collect.Lists; import net.sf.json.JSONObject; import hudson.model.Job; import hudson.model.AbstractProject; import hudson.model.ModelObject; import hudson.plugins.analysis.core.AbstractHealthDescriptor; import hudson.plugins.analysis.core.BuildHistory; import hudson.util.Graph; /** * Configuration properties of a trend graph. */ public abstract class GraphConfigurationView implements ModelObject { private static final Logger LOGGER = Logger.getLogger(GraphConfigurationView.class.getName()); /** The owning job to configure the graphs for. */ private final Job<?, ?> owner; private final String key; private final BuildHistory buildHistory; private final AbstractHealthDescriptor healthDescriptor; // NOPMD private final GraphConfiguration configuration; private String urlPrefix; /** * Creates a new instance of {@link GraphConfigurationView}. * * @param configuration * the graph configuration * @param job * the owning job to configure the graphs for * @param key * unique key of this graph * @param buildHistory * the build history for this job */ public GraphConfigurationView(final GraphConfiguration configuration, final Job<?, ?> job, final String key, final BuildHistory buildHistory) { this.configuration = configuration; this.owner = job; this.key = key; this.buildHistory = buildHistory; healthDescriptor = buildHistory.getHealthDescriptor(); } /** * Creates a new instance of {@link GraphConfigurationView}. * * @param configuration * the graph configuration * @param project * the owning project to configure the graphs for * @param key * unique key of this graph * @param buildHistory * the build history for this project * @deprecated use * {@link #GraphConfigurationView(GraphConfiguration, Job, String, BuildHistory)} */ @Deprecated public GraphConfigurationView(final GraphConfiguration configuration, final AbstractProject<?, ?> project, final String key, final BuildHistory buildHistory) { this(configuration, (Job<?, ?>) project, key, buildHistory); } /** * Creates a file with for the default values. * * @param job * the job used as directory for the file * @param pluginName * the name of the plug-in * @return the created file */ protected static File createDefaultsFile(final Job<?, ?> job, final String pluginName) { return new File(job.getRootDir(), pluginName + ".txt"); } /** * Creates a file with for the default values. * * @param project * the project used as directory for the file * @param pluginName * the name of the plug-in * @return the created file * * @deprecated use * {@link #createDefaultsFile(Job, String)} */ @Deprecated protected static File createDefaultsFile(final AbstractProject<?, ?> project, final String pluginName) { return createDefaultsFile((Job<?, ?>) project, pluginName); } /** * Returns the list of available graphs. * * @return the list of available graphs */ public List<? extends BuildResultGraph> getAvailableGraphs() { ArrayList<BuildResultGraph> selectable = Lists.newArrayList(); for (BuildResultGraph graph : configuration.getRegisteredGraphs()) { if (graph.isSelectable()) { selectable.add(graph); } } return selectable; } /** * Returns the owner. * * @return the owner */ @WithBridgeMethods(value=AbstractProject.class, adapterMethod="getAbstractProject") public Job<?, ?> getOwner() { return owner; } /** * Added for backward compatibility. It generates <pre>AbstractProject getOwner()</pre> bytecode during the build * process, so old implementations can use that signature. * * @see {@link WithBridgeMethods} */ @Deprecated private Object getAbstractProject(final Job owner, final Class targetClass) { return owner instanceof AbstractProject ? (AbstractProject) owner : null; } /** * Returns the key of this graph. * * @return the key */ public String getKey() { return key; } /** * Returns the description for this view. * * @return the description for this view */ public abstract String getDescription(); /** * Saves the configured values. Subclasses need to implement the actual persistence. * * @param request * Stapler request * @param response * Stapler response */ public void doSave(final StaplerRequest request, final StaplerResponse response) { try { JSONObject formData = request.getSubmittedForm(); if (configuration.initializeFrom(formData)) { persistValue(configuration.serializeToString(), key, request, response); } } catch (IOException exception) { LOGGER.log(Level.SEVERE, "Can't save the form data: " + request, exception); } catch (ServletException exception) { LOGGER.log(Level.SEVERE, "Can't process the form data: " + request, exception); } finally { try { response.sendRedirect(owner.getAbsoluteUrl()); } catch (IOException exception) { LOGGER.log(Level.SEVERE, "Can't redirect", exception); } } } /** * Checks whether a meaningful graph is available. * * @return <code>true</code>, if there is such a graph */ public boolean hasMeaningfulGraph() { if (buildHistory.hasPreviousResult()) { return !BuildResultGraph.areResultsTooOld(configuration, buildHistory.getPreviousResult()); } return false; } /** * Persists the configured values. * * @param value * the values configured by the user. * @param pluginName * the name of the plug-in * @param request * Stapler request * @param response * Stapler response * @throws IOException * if the values could not be persisted */ protected abstract void persistValue(String value, String pluginName, StaplerRequest request, StaplerResponse response) throws IOException; /** * This method will be called by Stapler if an example image for the * specified graph should be rendered. * * @param graphId * the ID of the graph to render * @param request * Stapler request * @param response * Stapler response * @return <code>null</code> */ public Object getDynamic(final String graphId, final StaplerRequest request, final StaplerResponse response) { try { BuildResultGraph graph = configuration.getGraph(graphId); if (hasMeaningfulGraph() && graph.isVisible()) { return graph.getGraph(-1, configuration, null, buildHistory.getBaseline()); } response.sendRedirect2(request.getContextPath() + graph.getExampleImage()); } catch (IOException exception) { LOGGER.log(Level.SEVERE, "Can't create graph: " + request, exception); } return null; } /** * Returns the graph renderer of the specified graph. * * @param graph * the graph * @param url * the URL of links in the trend graph * @return the graph renderer of the specified graph */ public Graph getGraphRenderer(final BuildResultGraph graph, final String url) { return graph.getGraph(getTimestamp(), configuration, url, buildHistory.getBaseline()); } /** * Returns the graph renderer of the current graph. * * @return the graph renderer of the current graph */ public Graph getGraphRenderer() { return getGraphRenderer(getGraphType(), null); } /** * Returns the graph renderer of the current graph. * * @param url * the URL of links in the trend graph * @return the graph renderer of the current graph */ public Graph getGraphRenderer(final String url) { return getGraphRenderer(getGraphType(), url); } /** * Checks if the health graph is available. * * @return <code>true</code>, if the health graph is available */ public boolean isHealthGraphAvailable() { return healthDescriptor.isEnabled(); } /** * Returns the height. * * @return the height */ public int getHeight() { return configuration.getHeight(); } /** * Returns the width. * * @return the width */ public int getWidth() { return configuration.getWidth(); } /** * Returns whether the build date or the build number should be used as domain. * * @return the build date or the build number should be used as domain */ public boolean getUseBuildDateAsDomain() { return configuration.useBuildDateAsDomain(); } /** * Returns the time stamp of the associated build. * * @return the time stamp of the associated build. */ public long getTimestamp() { return buildHistory.getTimestamp().getTimeInMillis(); } /** * Returns the number of builds to consider. * * @return the number of builds to consider */ public int getBuildCount() { return configuration.getBuildCount(); } /** * Returns the number of builds to consider. * * @return the number of builds to consider */ public String getBuildCountString() { return getStringValue(getBuildCount()); } /** * Returns the value as integer. If the value is 0, then an empty string is * returned. * * @param value * the value to convert * @return string representation of <code>value</code> */ private String getStringValue(final int value) { return value == 0 ? StringUtils.EMPTY : String.valueOf(value); } /** * Returns the number of days to consider. * * @return the number of days to consider */ public int getDayCount() { return configuration.getDayCount(); } /** * Returns the number of days to consider. * * @return the number of days to consider */ public String getDayCountString() { return getStringValue(getDayCount()); } /** * Returns the parameter name to consider. * * @return the parameter name to consider. */ public String getParameterName() { return configuration.getParameterName(); } /** * Returns the parameter value to consider. * * @return the parameter value to consider. */ public String getParameterValue() { return configuration.getParameterValue(); } /** * Returns the type of the graph. * * @return the type */ public BuildResultGraph getGraphType() { BuildResultGraph graphType = configuration.getGraphType(); graphType.setRootUrl(StringUtils.defaultString(urlPrefix)); return graphType; } /** * Returns whether the trend graph is visible or not. * * @return <code>true</code>, if the trend graph is visible, <code>false</code> otherwise */ public boolean isVisible() { return getGraphType().isVisible(); } /** * Returns whether the trend graph completely is deactivated. * * @return <code>true</code>, if the trend graph is deactivated, <code>false</code> otherwise */ public boolean isDeactivated() { return getGraphType().isDeactivated(); } @Override public String toString() { return configuration.toString(); } /** * Returns the current health descriptor. * * @return the health descriptor */ public AbstractHealthDescriptor getHealthDescriptor() { return healthDescriptor; } /** * Sets the prefix of the URLs in the trend graph. Depending on the sub page this trend is shown a different * prefix can be set for the relative URL. * * @param urlPrefix prefix, might be empty * @since 1.73 */ public void setUrlPrefix(final String urlPrefix) { this.urlPrefix = urlPrefix; } }