/* * eXist Open Source Native XML Database * Copyright (C) 2009 The eXist Project * http://exist-db.org * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * $Id$ */ package org.exist.xquery.modules.jfreechart; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import org.apache.log4j.Logger; import org.exist.dom.QName; import org.exist.validation.internal.node.NodeInputStream; import org.exist.xquery.BasicFunction; import org.exist.xquery.Cardinality; import org.exist.xquery.FunctionSignature; import org.exist.xquery.Variable; import org.exist.xquery.XPathException; import org.exist.xquery.XQueryContext; import org.exist.xquery.functions.response.ResponseModule; import org.exist.xquery.value.FunctionReturnSequenceType; import org.exist.xquery.value.JavaObjectValue; import org.exist.xquery.value.Sequence; import org.exist.xquery.value.SequenceType; import org.exist.xquery.value.Type; import org.exist.external.org.apache.commons.io.output.ByteArrayOutputStream; import org.jfree.chart.ChartUtilities; import org.jfree.chart.JFreeChart; import org.exist.http.servlets.ResponseWrapper; import org.exist.storage.serializers.Serializer; import org.exist.xquery.value.Base64Binary; import org.exist.xquery.value.FunctionParameterSequenceType; import org.exist.xquery.value.NodeValue; /** * JFreechart extension functions. * * @author Dannes Wessels (dizzzz@exist-db.org) */ public class JFreeCharting extends BasicFunction { protected static final Logger logger = Logger.getLogger(JFreeCharting.class); private static final String function1Txt = "Render chart using JFreechart. Check documentation on " + "http://www.jfree.org/jfreechart/ for details about chart types, " + "parameters and data structures."; private static final String function2Txt = function1Txt + " Output is directly streamed into the servlet output stream."; private static final String chartText = "The type of chart to render. Supported chart types: LineChart, LineChart3D, " + "MultiplePieChart, MultiplePieChart3D, PieChart, PieChart3D, " + "RingChart, StackedAreaChart, StackedBarChart, StackedBarChart3D, " + "WaterfallChart."; private static final String parametersText = "The configuration for the chart. The configuration should be " + "supplied as follows: <configuration>"+ "<param1>Value1</param1> <param2>Value2</param2> </configuration>. " + "Supported parameters: width height title categoryAxisLabel " + "valueAxisLabel domainAxisLabel rangeAxisLabel orientation " + "order legend tooltips urls."; public final static FunctionSignature signatures[] = { new FunctionSignature( new QName("render", JFreeChartModule.NAMESPACE_URI, JFreeChartModule.PREFIX), function1Txt, new SequenceType[]{ new FunctionParameterSequenceType("chart-type", Type.STRING, Cardinality.EXACTLY_ONE, chartText), new FunctionParameterSequenceType("configuration", Type.NODE, Cardinality.EXACTLY_ONE, parametersText), new FunctionParameterSequenceType("data", Type.NODE, Cardinality.EXACTLY_ONE, "The CategoryDataset or PieDataset, supplied as JFreechart XML.") }, new FunctionReturnSequenceType(Type.BASE64_BINARY, Cardinality.ZERO_OR_ONE, "the generated PNG image file") ), new FunctionSignature( new QName("stream-render", JFreeChartModule.NAMESPACE_URI, JFreeChartModule.PREFIX), function2Txt, new SequenceType[]{ new FunctionParameterSequenceType("chart-type", Type.STRING, Cardinality.EXACTLY_ONE, chartText), new FunctionParameterSequenceType("configuration", Type.NODE, Cardinality.EXACTLY_ONE, parametersText), new FunctionParameterSequenceType("data", Type.NODE, Cardinality.EXACTLY_ONE, "The CategoryDataset or PieDataset, supplied as JFreechart XML.") }, new SequenceType(Type.EMPTY, Cardinality.EMPTY) ) }; public JFreeCharting(XQueryContext context, FunctionSignature signature) { super(context, signature); } @Override public Sequence eval(Sequence[] args, Sequence contextSequence) throws XPathException { //was an image and a mime-type speficifed if(args[1].isEmpty() || args[2].isEmpty()){ return Sequence.EMPTY_SEQUENCE; } try { // Get chart type String chartType = args[0].getStringValue(); // Get configuration Configuration config = new Configuration(); config.parse(((NodeValue)args[1].itemAt(0)).getNode()); // Get datastream Serializer serializer=context.getBroker().getSerializer(); NodeValue node = (NodeValue) args[2].itemAt(0); InputStream is = new NodeInputStream(serializer, node); // get chart JFreeChart chart = null; try { chart = JFreeChartFactory.createJFreeChart(chartType, config, is); } catch (IllegalArgumentException ex){ throw new XPathException(this, ex.getMessage()); } // Verify if chart is present if(chart==null){ throw new XPathException(this, "Unable to create chart '"+chartType+"'"); } // Render output if(isCalledAs("render")){ byte[] image=writePNG(config, chart); return new Base64Binary(image); } else { ResponseWrapper response = getResponseWrapper(context); writePNGtoResponse(config, response, chart); } } catch (Exception ex) { LOG.error(ex); throw new XPathException(this, ex.getMessage()); } return Sequence.EMPTY_SEQUENCE; } /** * Get HTTP response wrapper which provides access to the servler * outputstream. * * @throws XPathException Thrown when something bad happens. */ private ResponseWrapper getResponseWrapper(XQueryContext context) throws XPathException { ResponseModule myModule = (ResponseModule) context.getModule(ResponseModule.NAMESPACE_URI); // response object is read from global variable $response Variable respVar = myModule.resolveVariable(ResponseModule.RESPONSE_VAR); if (respVar == null) { throw new XPathException(this, "No response object found in the current XQuery context."); } if (respVar.getValue().getItemType() != Type.JAVA_OBJECT) { throw new XPathException(this, "Variable $response is not bound to an Java object."); } JavaObjectValue respValue = (JavaObjectValue) respVar.getValue().itemAt(0); if (!"org.exist.http.servlets.HttpResponseWrapper".equals(respValue.getObject().getClass().getName())) { throw new XPathException(this, signatures[1].toString() + " can only be used within the EXistServlet or XQueryServlet"); } ResponseWrapper response = (ResponseWrapper) respValue.getObject(); return response; } /** * Writes chart to response wrapper as PNG image. * * @throws XPathException Thrown when an IO exception is thrown, */ private void writePNGtoResponse(Configuration config, ResponseWrapper response, JFreeChart chart) throws XPathException { OutputStream os = null; try { response.setContentType("image/png"); os = response.getOutputStream(); ChartUtilities.writeChartAsPNG(os, chart, config.getImageWidth(), config.getImageHeight()); os.close(); } catch (IOException ex) { LOG.error(ex); throw new XPathException(this, "IO issue while serializing image. " + ex.getMessage()); } finally { try { os.close(); } catch (IOException ex) { // Ignore LOG.debug(ex); } } } private byte[] writePNG(Configuration config, JFreeChart chart) throws IOException{ ByteArrayOutputStream os = new ByteArrayOutputStream(); ChartUtilities.writeChartAsPNG(os, chart, config.getImageWidth(), config.getImageHeight()); return os.toByteArray(); } }