/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.util;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.ChartRenderingInfo;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.plot.Plot;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import javax.servlet.ServletOutputStream;
import javax.imageio.ImageIO;
import java.io.IOException;
import java.util.Calendar;
import java.awt.image.BufferedImage;
import java.awt.*;
import javax.annotation.Nonnull;
import javax.annotation.CheckForNull;
/**
* A JFreeChart-generated graph that's bound to UI.
*
* <p>
* This object exposes two URLs:
* <dl>
* <dt>/png
* <dd>PNG image of a graph
*
* <dt>/map
* <dd>Clickable map
* </dl>
*
* @author Kohsuke Kawaguchi
* @since 1.320
*/
public abstract class Graph {
private final long timestamp;
private final int defaultW;
private final int defaultH;
private volatile JFreeChart graph;
/**
* @param timestamp
* Timestamp of this graph. Used for HTTP cache related headers.
* If the graph doesn't have any timestamp to tie it to, pass -1.
*/
protected Graph(long timestamp, int defaultW, int defaultH) {
this.timestamp = timestamp;
this.defaultW = defaultW;
this.defaultH = defaultH;
}
protected Graph(Calendar timestamp, int defaultW, int defaultH) {
this(timestamp.getTimeInMillis(),defaultW,defaultH);
}
/**
* Actually render a chart.
*/
protected abstract JFreeChart createGraph();
private BufferedImage render(StaplerRequest req, ChartRenderingInfo info) {
String w = req.getParameter("width");
if(w==null) w=String.valueOf(defaultW);
String h = req.getParameter("height");
if(h==null) h=String.valueOf(defaultH);
Color graphBg = stringToColor(req.getParameter("graphBg"));
Color plotBg = stringToColor(req.getParameter("plotBg"));
if (graph==null) graph = createGraph();
graph.setBackgroundPaint(graphBg);
Plot p = graph.getPlot();
p.setBackgroundPaint(plotBg);
return graph.createBufferedImage(Integer.parseInt(w),Integer.parseInt(h),info);
}
@Nonnull private static Color stringToColor(@CheckForNull String s) {
if (s != null) {
try {
return Color.decode("0x" + s);
} catch (NumberFormatException e) {
return Color.WHITE;
}
} else {
return Color.WHITE;
}
}
/**
* Renders a graph.
*/
public void doPng(StaplerRequest req, StaplerResponse rsp) throws IOException {
if (req.checkIfModified(timestamp, rsp)) return;
try {
BufferedImage image = render(req,null);
rsp.setContentType("image/png");
ServletOutputStream os = rsp.getOutputStream();
ImageIO.write(image, "PNG", os);
os.close();
} catch(Error e) {
/* OpenJDK on ARM produces an error like this in case of headless error
Caused by: java.lang.Error: Probable fatal error:No fonts found.
at sun.font.FontManager.getDefaultPhysicalFont(FontManager.java:1088)
at sun.font.FontManager.initialiseDeferredFont(FontManager.java:967)
at sun.font.CompositeFont.doDeferredInitialisation(CompositeFont.java:254)
at sun.font.CompositeFont.getSlotFont(CompositeFont.java:334)
at sun.font.CompositeStrike.getStrikeForSlot(CompositeStrike.java:77)
at sun.font.CompositeStrike.getFontMetrics(CompositeStrike.java:93)
at sun.font.Font2D.getFontMetrics(Font2D.java:387)
at java.awt.Font.defaultLineMetrics(Font.java:2082)
at java.awt.Font.getLineMetrics(Font.java:2152)
at org.jfree.chart.axis.NumberAxis.estimateMaximumTickLabelHeight(NumberAxis.java:974)
at org.jfree.chart.axis.NumberAxis.selectVerticalAutoTickUnit(NumberAxis.java:1104)
at org.jfree.chart.axis.NumberAxis.selectAutoTickUnit(NumberAxis.java:1048)
at org.jfree.chart.axis.NumberAxis.refreshTicksVertical(NumberAxis.java:1249)
at org.jfree.chart.axis.NumberAxis.refreshTicks(NumberAxis.java:1149)
at org.jfree.chart.axis.ValueAxis.reserveSpace(ValueAxis.java:788)
at org.jfree.chart.plot.CategoryPlot.calculateRangeAxisSpace(CategoryPlot.java:2650)
at org.jfree.chart.plot.CategoryPlot.calculateAxisSpace(CategoryPlot.java:2669)
at org.jfree.chart.plot.CategoryPlot.draw(CategoryPlot.java:2716)
at org.jfree.chart.JFreeChart.draw(JFreeChart.java:1222)
at org.jfree.chart.JFreeChart.createBufferedImage(JFreeChart.java:1396)
at org.jfree.chart.JFreeChart.createBufferedImage(JFreeChart.java:1376)
at org.jfree.chart.JFreeChart.createBufferedImage(JFreeChart.java:1361)
at hudson.util.ChartUtil.generateGraph(ChartUtil.java:116)
at hudson.util.ChartUtil.generateGraph(ChartUtil.java:99)
at hudson.tasks.test.AbstractTestResultAction.doPng(AbstractTestResultAction.java:196)
at hudson.tasks.test.TestResultProjectAction.doTrend(TestResultProjectAction.java:97)
... 37 more
*/
if(e.getMessage().contains("Probable fatal error:No fonts found")) {
rsp.sendRedirect2(req.getContextPath()+"/images/headless.png");
return;
}
throw e; // otherwise let the caller deal with it
} catch(HeadlessException e) {
// not available. send out error message
rsp.sendRedirect2(req.getContextPath()+"/images/headless.png");
}
}
/**
* Renders a clickable map.
*/
public void doMap(StaplerRequest req, StaplerResponse rsp) throws IOException {
if (req.checkIfModified(timestamp, rsp)) return;
ChartRenderingInfo info = new ChartRenderingInfo();
render(req,info);
rsp.setContentType("text/plain;charset=UTF-8");
rsp.getWriter().println(ChartUtilities.getImageMap( "map", info ));
}
}