/*! * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * 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. * * Copyright (c) 2002-2013 Pentaho Corporation.. All rights reserved. */ package org.pentaho.platform.plugin.action.chartbeans; import org.pentaho.chart.AbstractChartThemeFactory; import org.pentaho.chart.ChartBeanFactory; import org.pentaho.chart.ChartBoot; import org.pentaho.chart.IChartLinkGenerator; import org.pentaho.chart.InvalidChartDefinition; import org.pentaho.chart.data.IChartDataModel; import org.pentaho.chart.model.ChartModel; import org.pentaho.chart.model.DialPlot; import org.pentaho.chart.model.PiePlot; import org.pentaho.chart.model.Theme; import org.pentaho.chart.model.util.ChartSerializer; import org.pentaho.chart.model.util.ChartSerializer.ChartSerializationFormat; import org.pentaho.chart.plugin.ChartDataOverflowException; import org.pentaho.chart.plugin.ChartProcessingException; import org.pentaho.chart.plugin.NoChartDataException; import org.pentaho.chart.plugin.api.IOutput; import org.pentaho.chart.plugin.api.IOutput.OutputTypes; import org.pentaho.chart.plugin.api.PersistenceException; import org.pentaho.chart.plugin.jfreechart.JFreeChartPlugin; import org.pentaho.chart.plugin.jfreechart.outputs.JFreeChartOutput; import org.pentaho.chart.plugin.openflashchart.OpenFlashChartPlugin; import org.pentaho.commons.connection.IPentahoResultSet; import org.pentaho.platform.api.engine.IPentahoRequestContext; import org.pentaho.platform.engine.core.system.PentahoRequestContextHolder; import org.pentaho.platform.engine.core.system.PentahoSessionHolder; import org.pentaho.platform.engine.core.system.PentahoSystem; import org.pentaho.platform.plugin.action.messages.Messages; import org.pentaho.reporting.libraries.resourceloader.ResourceException; import javax.imageio.ImageIO; import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; /** * This is a bean that permits easy access to the ChartBeans functionality and was specifically designed to be run from * within the Pentaho Platform as an Action Sequence Component. * * @author cboyden * * @deprecated please use {@link ChartAction} * */ public class ChartComponent { protected static final int DEFAULT_CHART_WIDTH = 400; protected static final int DEFAULT_CHART_HEIGHT = 300; protected boolean convertNullsToZero = false; protected String seriesColumnName = null; protected int seriesColumn = -1; protected String categoryColumnName = null; protected int categoryColumn = -1; protected String valueColumnName = null; protected int valueColumn = -1; protected IPentahoResultSet resultSet = null; protected String chartEngine; protected Exception bootException = null; protected String outputType = ""; //$NON-NLS-1$ protected int chartWidth = -1; protected int chartHeight = -1; protected Number scalingFactor = new Double( 1 ); protected OutputStream outputStream = null; protected String chartModelJson = null; protected String chartModelXml = null; protected ChartModel chartModel = null; protected String contentLinkingTemplate; protected String title = null; // private String flashPath = "openflashchart"; //$NON-NLS-1$ // private String flashSwf = "open-flash-chart-full-embedded-font.swf"; //$NON-NLS-1$s /** * Initialize ChartBeans engine */ { synchronized ( ChartBoot.getInstance() ) { while ( !ChartBoot.getInstance().isBootDone() ) { if ( ChartBoot.getInstance().isBootInProgress() ) { // Wait 1 second try { java.lang.Thread.sleep( 1000 ); } catch ( InterruptedException e ) { // Do nothing } } else { if ( !ChartBoot.getInstance().isBootFailed() ) { ChartBoot.getInstance().start(); } } } // End while: boot is not done // Check for an error if ( ChartBoot.getInstance().isBootFailed() ) { bootException = ChartBoot.getInstance().getBootFailureReason(); } } // End thread synchronization } /** * Called to process the chart definition and data set to produce a usable chart. * * @return state of execution. 'true' if execution was successful, otherwise false. * @throws ChartBootException * @throws ChartProcessingException * @throws ResourceException * @throws InvalidChartDefinition * @throws IOException * @throws PersistenceException */ public boolean execute() throws ChartBootException, ChartProcessingException, ResourceException, InvalidChartDefinition, IOException, PersistenceException { if ( bootException != null ) { throw new ChartBootException( bootException ); } if ( chartModel.getTheme() != null ) { AbstractChartThemeFactory chartThemeFactory = new AbstractChartThemeFactory() { protected List<File> getThemeFiles() { ArrayList<File> themeFiles = new ArrayList<File>(); themeFiles.add( new File( PentahoSystem.getApplicationContext().getSolutionPath( "system/chartbeans/themes/Theme1.xml" ) ) ); //$NON-NLS-1$ themeFiles.add( new File( PentahoSystem.getApplicationContext().getSolutionPath( "system/chartbeans/themes/Theme2.xml" ) ) ); //$NON-NLS-1$ themeFiles.add( new File( PentahoSystem.getApplicationContext().getSolutionPath( "system/chartbeans/themes/Theme3.xml" ) ) ); //$NON-NLS-1$ themeFiles.add( new File( PentahoSystem.getApplicationContext().getSolutionPath( "system/chartbeans/themes/Theme4.xml" ) ) ); //$NON-NLS-1$ themeFiles.add( new File( PentahoSystem.getApplicationContext().getSolutionPath( "system/chartbeans/themes/Theme5.xml" ) ) ); //$NON-NLS-1$ themeFiles.add( new File( PentahoSystem.getApplicationContext().getSolutionPath( "system/chartbeans/themes/Theme6.xml" ) ) ); //$NON-NLS-1$ themeFiles.add( new File( PentahoSystem.getApplicationContext().getSolutionPath( "system/chartbeans/themes/Theme7.xml" ) ) ); //$NON-NLS-1$ themeFiles.add( new File( PentahoSystem.getApplicationContext().getSolutionPath( "system/chartbeans/themes/Theme8.xml" ) ) ); //$NON-NLS-1$ return themeFiles; } }; if ( !( chartModel.getPlot() instanceof DialPlot ) ) { Theme chartTheme = chartThemeFactory.getTheme( chartModel.getTheme() ); if ( chartTheme != null ) { chartTheme.applyTo( chartModel ); } } } // Make sure chart engine is loaded loadChartEngine(); // Set chart engine on chartModel for the ChartFactory to use chartModel.setChartEngineId( chartEngine ); InputStream is = null; // Transform IPentahoResultSet to an object array Object[][] data = processChartData( resultSet, valueColumn ); try { IChartLinkGenerator chartLinkGenerator = contentLinkingTemplate == null ? null : new ChartLinkGenerator( contentLinkingTemplate ); IChartDataModel chartDataModel = ChartBeanFactory.createChartDataModel( data, scalingFactor, convertNullsToZero, valueColumn, seriesColumn, categoryColumn, chartModel, resultSet.getMetaData() ); IOutput output = ChartBeanFactory.createChart( chartModel, chartDataModel, chartLinkGenerator ); // Wrap output as necessary if ( OpenFlashChartPlugin.PLUGIN_ID.equals( chartEngine ) ) { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); output.persistChart( outputStream, getOutputType(), chartWidth, chartHeight ); String persistedChart = new String( outputStream.toByteArray(), "utf-8" ); //$NON-NLS-1$ IPentahoRequestContext requestContext = PentahoRequestContextHolder.getRequestContext(); String flashContent = ChartBeansGeneratorUtil.mergeOpenFlashChartHtmlTemplate( persistedChart.replaceAll( "\"", "\\\\\"" ), //$NON-NLS-1$ //$NON-NLS-2$ requestContext.getContextPath() + this.getSwfPath() + "/" + getSwfName() ); //$NON-NLS-1$ is = new ByteArrayInputStream( flashContent.getBytes( "utf-8" ) ); //$NON-NLS-1$ } else if ( JFreeChartPlugin.PLUGIN_ID.equals( chartEngine ) ) { if ( "html".equals( outputType ) || ( chartLinkGenerator != null ) ) { //$NON-NLS-1$ File imageFile = PentahoSystem.getApplicationContext().createTempFile( PentahoSessionHolder.getSession(), "tmp_chart_", ".png", false ); //$NON-NLS-1$ FileOutputStream outputStream = new FileOutputStream( imageFile ); output.persistChart( outputStream, OutputTypes.FILE_TYPE_PNG, chartWidth, chartHeight ); String imageMapName = null; String imageMap = null; if ( chartLinkGenerator != null ) { imageMapName = imageFile.getName().substring( 0, imageFile.getName().indexOf( '.' ) ); imageMap = ( (JFreeChartOutput) output ).getMap( imageMapName ); } String jFreeChartHtml = ChartBeansGeneratorUtil.mergeJFreeChartHtmlTemplate( imageFile, imageMap, imageMapName, chartWidth, chartHeight, PentahoRequestContextHolder.getRequestContext().getContextPath() ); is = new ByteArrayInputStream( jFreeChartHtml.getBytes( "utf-8" ) ); //$NON-NLS-1$ } else { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); output.persistChart( outputStream, getOutputType(), chartWidth, chartHeight ); is = new ByteArrayInputStream( outputStream.toByteArray() ); } } int val = 0; // TODO: Buffer for more efficiency while ( ( val = is.read() ) != -1 ) { outputStream.write( val ); } } catch ( NoChartDataException ex ) { if ( JFreeChartPlugin.PLUGIN_ID.equals( chartEngine ) ) { BufferedImage image = new BufferedImage( chartWidth, chartHeight, BufferedImage.TYPE_INT_ARGB ); Graphics2D graphics = image.createGraphics(); graphics.setFont( new Font( "serif", Font.BOLD, 14 ) ); //$NON-NLS-1$ graphics.setColor( Color.BLACK ); graphics.drawString( "The chart data query returned no data.", 40, 40 ); //$NON-NLS-1$ String outputType = "png"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ File imageFile = PentahoSystem.getApplicationContext().createTempFile( PentahoSessionHolder.getSession(), "tmp_chart_", ".png", false ); //$NON-NLS-1$ FileOutputStream fo = new FileOutputStream( imageFile ); ImageIO.write( image, "png", fo ); String jFreeChartHtml = ChartBeansGeneratorUtil.mergeJFreeChartHtmlTemplate( imageFile, null, null, chartWidth, chartHeight, PentahoRequestContextHolder.getRequestContext().getContextPath() ); is = new ByteArrayInputStream( jFreeChartHtml.getBytes( "utf-8" ) ); //$NON-NLS-1$ int val = 0; while ( ( val = is.read() ) != -1 ) { outputStream.write( val ); } } else { String flashContent = ChartBeansGeneratorUtil.buildEmptyOpenFlashChartHtmlFragment( "The chart data query returned no data." ); //$NON-NLS-1$ is = new ByteArrayInputStream( flashContent.getBytes( "utf-8" ) ); //$NON-NLS-1$ int val = 0; // TODO: Buffer for more efficiency while ( ( val = is.read() ) != -1 ) { outputStream.write( val ); } } } catch ( ChartDataOverflowException ex ) { if ( JFreeChartPlugin.PLUGIN_ID.equals( chartEngine ) ) { BufferedImage image = new BufferedImage( chartWidth, chartHeight, BufferedImage.TYPE_INT_ARGB ); Graphics2D graphics = image.createGraphics(); graphics.setFont( new Font( "serif", Font.BOLD, 14 ) ); //$NON-NLS-1$ graphics.setColor( Color.BLACK ); graphics.drawString( Messages.getInstance().getErrorString( "ChartComponent.TOO_MANY_DATA_POINTS" ), 5, 5 ); //$NON-NLS-1$ graphics.drawString( Messages.getInstance().getErrorString( "ChartComponent.MAX_ALLOWED_DATA_POINTS", Integer.toString( ex.getMaxAllowedDataPoints() ) ), 5, 25 ); //$NON-NLS-1$ String outputType = getMimeType().equals( "image/jpg" ) ? "jpeg" : "png"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ ImageIO.write( image, outputType, outputStream ); } else { String flashContent = ChartBeansGeneratorUtil.buildEmptyOpenFlashChartHtmlFragment( Messages.getInstance().getErrorString( "ChartComponent.TOO_MANY_DATA_POINTS_HTML", Integer.toString( ex.getMaxAllowedDataPoints() ) ) ); //$NON-NLS-1$ is = new ByteArrayInputStream( flashContent.getBytes( "utf-8" ) ); //$NON-NLS-1$ int val = 0; // TODO: Buffer for more efficiency while ( ( val = is.read() ) != -1 ) { outputStream.write( val ); } } } return true; } /** * Transform the IPentahoResultSet into the data format suitable for chart creation. * * @return Row / Column data table or null */ protected Object[][] processChartData( IPentahoResultSet resultSet, int valueColumnIndex ) { if ( resultSet == null ) { return null; } Object[][] result = null; result = new Object[resultSet.getRowCount()][resultSet.getMetaData().getColumnCount()]; for ( int r = 0; r < resultSet.getRowCount(); r++ ) { for ( int c = 0; c < resultSet.getMetaData().getColumnCount(); c++ ) { result[r][c] = resultSet.getValueAt( r, c ); } } return ( result ); } /** * Define the OutputStream to which the resulting chart shall be written * * @param outStream * Stream receive the chart */ public void setOutputStream( OutputStream outStream ) { outputStream = outStream; } /** * Define the data set that will populate the chart * * @param chartDataSet * data set for charting */ public void setChartData( IPentahoResultSet chartDataSet ) { resultSet = chartDataSet.memoryCopy(); } /** * Validate the current settings of the ChartComponent. If validate() returns true, then execute may be called. If * validate() returns false, a call to execute() is guaranteed to fail. * * @return state of validation * @throws Exception */ public boolean validate() throws Exception { // Must have a valid result set if ( resultSet == null ) { return false; } // Default to the first three columns if no others are explicitly specified // Resolve column name to column ordinal if present if ( seriesColumnName != null ) { // Leave it at -1 if it is specified as blank (The charting engine will handle this properly) if ( !seriesColumnName.equals( "" ) ) { //$NON-NLS-1$ seriesColumn = resultSet.getMetaData().getColumnIndex( seriesColumnName ); } } else { // Set default ordering as no ordinal has been defined if ( seriesColumn < 0 ) { seriesColumn = 0; } } if ( categoryColumnName != null ) { // Leave it at -1 if it is specified as blank (The charting engine will handle this properly) if ( "None".equals( categoryColumnName ) ) { categoryColumn = -1; } else if ( !categoryColumnName.equals( "" ) ) { //$NON-NLS-1$ categoryColumn = resultSet.getMetaData().getColumnIndex( categoryColumnName ); } } else { // Set default ordering as no ordinal has been defined if ( categoryColumn < 0 ) { categoryColumn = 1; } } if ( valueColumnName != null ) { // Leave it at -1 if it is specified as blank (The charting engine will handle this properly) if ( !valueColumnName.equals( "" ) ) { //$NON-NLS-1$ valueColumn = resultSet.getMetaData().getColumnIndex( valueColumnName ); } } else { // Set default ordering as no ordinal has been defined if ( valueColumn < 0 ) { valueColumn = 2; } } loadChartEngine(); if ( chartModel == null ) { return false; } // Verify that all columns required for a given chart type are present if ( chartModel.getPlot() instanceof DialPlot ) { if ( valueColumn < 0 ) { return false; } } else if ( chartModel.getPlot() instanceof PiePlot ) { if ( ( seriesColumn < 0 ) || ( valueColumn < 0 ) ) { return false; } } else { if ( ( seriesColumn < 0 ) || ( valueColumn < 0 ) ) { return false; } } if ( chartWidth <= 0 ) { chartWidth = DEFAULT_CHART_WIDTH; } if ( chartHeight <= 0 ) { chartHeight = DEFAULT_CHART_HEIGHT; } return true; } /** * Define the column in the data set that contains the Series/Domain data * * @param seriesCol * name of column that contains the Series/Domain for the chart */ public void setSeriesColumn( String seriesCol ) { seriesColumnName = seriesCol; } public void setConvertNullsToZero( boolean convert ) { this.convertNullsToZero = convert; } public boolean getConvertNullsToZero() { return convertNullsToZero; } /** * Define the column in the data set that contains the Category data * * @param seriesCol * name of column that contains the Category for the chart */ public void setCategoryColumn( String categoryCol ) { categoryColumnName = categoryCol; } /** * Define the column in the data set that contains the Value/Range data * * @param seriesCol * name of column that contains the Value/Range for the chart */ public void setValueColumn( String valueCol ) { valueColumnName = valueCol; } /** * Fetch the desired output type * * @return output type */ protected OutputTypes getOutputType() { if ( outputType.equals( "jpg" ) ) { //$NON-NLS-1$ return OutputTypes.FILE_TYPE_JPEG; } else if ( outputType.equals( "png" ) ) { //$NON-NLS-1$ return OutputTypes.FILE_TYPE_PNG; } else if ( outputType.equals( "html" ) ) { //$NON-NLS-1$ return OutputTypes.FILE_TYPE_HTML; } return null; } /** * Fetch the desired MimeType * * @return mime type */ public String getMimeType() { loadChartEngine(); if ( JFreeChartPlugin.PLUGIN_ID.equals( chartEngine ) ) { if ( outputType.equalsIgnoreCase( "jpg" ) ) { //$NON-NLS-1$ return "image/jpg"; //$NON-NLS-1$ } else if ( outputType.equalsIgnoreCase( "png" ) ) { //$NON-NLS-1$ return "image/png"; //$NON-NLS-1$ } else if ( outputType.equalsIgnoreCase( "html" ) ) { //$NON-NLS-1$ return "text/html"; //$NON-NLS-1$ } // Default JFREE action outputType = "png"; //$NON-NLS-1$ return "image/png"; //$NON-NLS-1$ } else if ( OpenFlashChartPlugin.PLUGIN_ID.equals( chartEngine ) ) { outputType = "html"; //$NON-NLS-1$ return "text/html"; //$NON-NLS-1$ } // Final component default is OFC return "text/html"; //$NON-NLS-1$ } /** * Sets the chart engine based on the order of precedence: 1) Chart Definition 2) Action Sequence 3) System Setting 4) * Hard Coded */ protected void loadChartEngine() { loadChartModel(); if ( chartModel != null ) { if ( chartModel.getChartEngineId() != null ) { this.chartEngine = chartModel.getChartEngineId(); // Defined in ChartModel, escape return; } } if ( this.chartEngine != null ) { // Engine set on Action Sequence, escape return; } // Load default value from system setting or take hard coded // Hard coded final fall back is Open Flash Chart String defaultChartEngine = PentahoSystem.getSystemSetting( "chartbeans/chartbeans_config.xml", "default-chart-engine", OpenFlashChartPlugin.PLUGIN_ID ); //$NON-NLS-1$ //$NON-NLS-2$ if ( defaultChartEngine == null ) { defaultChartEngine = OpenFlashChartPlugin.PLUGIN_ID; } this.chartEngine = defaultChartEngine; } protected void loadChartModel() { if ( chartModel == null ) { if ( chartModelJson != null ) { chartModel = ChartSerializer.deSerialize( chartModelJson, ChartSerializationFormat.JSON ); } else { if ( chartModelXml != null ) { chartModel = ChartSerializer.deSerialize( chartModelXml, ChartSerializationFormat.XML ); } } } } /** * Set the JSON representation of the ChartModel * * @param chartModelJson * JSON serialized representation of the ChartModel */ public void setChartModelJson( String chartModelJson ) { this.chartModelJson = chartModelJson; } /** * Set the XML representation of the ChartModel * * @param chartStyleXml * XML serialized representation of the ChartModel */ public void setChartModelXml( String chartModelXml ) { this.chartModelXml = chartModelXml; } /** * Set the ChartModel * * @param chartModel * model of the chart to be generated */ public void setChartModel( ChartModel chartModel ) { this.chartModel = chartModel; } /** * Set the width of the chart in units specific to the ChartPlugin * * @param chartWidth * width of the chart */ public void setChartWidth( int chartWidth ) { this.chartWidth = chartWidth; } /** * Set the height of the chart in units specific to the ChartPlugin * * @param chartHeight * height of the chart */ public void setChartHeight( int chartHeight ) { this.chartHeight = chartHeight; } /** * Set the width of the chart in units specific to the ChartPlugin * * @param chartWidth * width of the chart */ public void setChartWidth( String chartWidth ) { this.chartWidth = Integer.valueOf( chartWidth ); } /** * Set the height of the chart in units specific to the ChartPlugin * * @param chartHeight * height of the chart */ public void setChartHeight( String chartHeight ) { this.chartHeight = Integer.valueOf( chartHeight ); } /** * Get the chart engine that the resulting chart was created through * * @return */ public String getChartEngine() { loadChartEngine(); return chartEngine; } /** * Set the chart engine to render the chart * * @param chartEngine * Value of "JFreeChart" or "OpenFlashChart" */ public void setChartEngine( String chartEngine ) { this.chartEngine = chartEngine; } public void setOutputType( String outputType ) { this.outputType = outputType; } public void setScalingFactor( Double scalingFactor ) { this.scalingFactor = scalingFactor; } public String getSwfPath() { return "openflashchart"; //$NON-NLS-1$ } public String getSwfName() { return "open-flash-chart-full-embedded-font.swf"; //$NON-NLS-1$ } public void setContentLinkingTemplate( String template ) { contentLinkingTemplate = template; } }