/* * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU General Public License, version 2 as published by the Free Software * Foundation. * * You should have received a copy of the GNU General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/gpl-2.0.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 General Public License for more details. * * * Copyright 2006 - 2008 Pentaho Corporation. All rights reserved. * * @created Apr 17, 2006 * @author James Dixon */ package org.pentaho.platform.uifoundation.chart; import java.io.File; import java.util.ArrayList; import java.util.StringTokenizer; import org.dom4j.Document; import org.dom4j.Node; import org.pentaho.commons.connection.DataUtilities; import org.pentaho.commons.connection.IPentahoConnection; import org.pentaho.commons.connection.IPentahoResultSet; import org.pentaho.platform.api.engine.IActionSequenceResource; import org.pentaho.platform.api.engine.ILogger; import org.pentaho.platform.api.engine.IMessageFormatter; import org.pentaho.platform.api.engine.IParameterProvider; import org.pentaho.platform.api.engine.IPentahoSession; import org.pentaho.platform.engine.core.solution.ActionInfo; import org.pentaho.platform.engine.core.system.PentahoSystem; import org.pentaho.platform.engine.services.actionsequence.ActionSequenceResource; import org.pentaho.platform.engine.services.connection.PentahoConnectionFactory; import org.pentaho.platform.engine.services.runtime.TemplateUtil; import org.pentaho.platform.uifoundation.messages.Messages; import org.pentaho.platform.util.messages.LocaleHelper; import org.pentaho.platform.util.web.SimpleUrlFactory; /** * This class provides wrapper functions to make it easier to execute action * sequences and generate a widget. */ public class ChartHelper { /** * doChart generates the images and html necessary to render various charts within a web page. * * @param solutionName the solution name * @param actionPath the action path * @param chartName the xml file describing the chart * @param parameterProvider the collection of parameters to customize the chart * @param outputStream the output string buffer for the content * @param userSession the user session object * @param messages a collection to store error and logging messages * @param logger logging object * * @return true if successful */ public static boolean doChart(final String solutionName, final String actionPath, final String chartName, final IParameterProvider parameterProvider, final StringBuffer outputStream, final IPentahoSession userSession, final ArrayList messages, ILogger logger) { boolean result = true; String content = null; StringBuffer messageBuffer = new StringBuffer(); if (logger == null) { // No logger? The usersession extends ILogger, use it for logging logger = userSession; } // Retrieve all parameters from parameter provider String outerParams = parameterProvider.getStringParameter("outer-params", null); //$NON-NLS-1$ String innerParam = parameterProvider.getStringParameter("inner-param", null); //$NON-NLS-1$ String urlDrillTemplate = parameterProvider.getStringParameter("drill-url", null); //$NON-NLS-1$ String imageUrl = parameterProvider.getStringParameter("image-url", null); //$NON-NLS-1$ // Very likely null; allow API users to continue to pass the dial value via parameters String dialValue = parameterProvider.getStringParameter("value", null); //$NON-NLS-1$ if (imageUrl == null) { imageUrl = PentahoSystem.getApplicationContext().getBaseUrl(); } if (urlDrillTemplate == null) { urlDrillTemplate = ""; //$NON-NLS-1$ } int width = (int) parameterProvider.getLongParameter("image-width", 150); //$NON-NLS-1$ int height = (int) parameterProvider.getLongParameter("image-height", 150); //$NON-NLS-1$ SimpleUrlFactory urlFactory = new SimpleUrlFactory(urlDrillTemplate); // Determine the type of chart we are building; these values can come from the chart xml definition, or // from the parameter provider. Try the parameter provider first, for performance reasons. String chartTypeStr = parameterProvider.getStringParameter(ChartDefinition.TYPE_NODE_NAME, null); String datasetType = ChartDefinition.CATEGORY_DATASET_STR; String definitionPath = solutionName + File.separator + actionPath + File.separator + chartName; if ((chartTypeStr == null) || (chartTypeStr.length() == 0)) { // load the XML document that defines the chart IActionSequenceResource resource = new ActionSequenceResource(chartName, IActionSequenceResource.SOLUTION_FILE_RESOURCE, "text/xml", //$NON-NLS-1$ definitionPath); try { // attempt to get the chart type and possibly data type from the xml doc Document chartDefinition = AbstractJFreeChartComponent.getResourceAsDocument(userSession, resource); Node chartAttributes = chartDefinition.selectSingleNode("//" + AbstractChartComponent.CHART_NODE_NAME); //$NON-NLS-1$ chartTypeStr = chartAttributes.selectSingleNode(ChartDefinition.TYPE_NODE_NAME).getText(); Node datasetTypeNode = chartAttributes.selectSingleNode(ChartDefinition.DATASET_TYPE_NODE_NAME); if (datasetTypeNode != null) { datasetType = datasetTypeNode.getText(); } } catch (Exception e) { logger.error(Messages.getInstance().getErrorString("ChartHelper.ERROR_0001_IO_PROBLEM_GETTING_CHART_TYPE"), e); //$NON-NLS-1$ PentahoSystem .get(IMessageFormatter.class, userSession) .formatErrorMessage( "text/html", Messages.getInstance().getString("ChartHelper.ERROR_0001_IO_PROBLEM_GETTING_CHART_TYPE"), messages, messageBuffer); //$NON-NLS-1$ //$NON-NLS-2$ content = messageBuffer.toString(); result = false; } } // Check again - do we have a chart type now? If not, bail out, we have no idea what to try to generate if ((chartTypeStr == null) || (chartTypeStr.length() == 0)) { logger.error(Messages.getInstance().getString("ChartHelper.ERROR_0002_COULD_NOT_DETERMINE_CHART_TYPE")); //$NON-NLS-1$ PentahoSystem .get(IMessageFormatter.class, userSession) .formatErrorMessage( "text/html", Messages.getInstance().getString("ChartHelper.ERROR_0002_COULD_NOT_DETERMINE_CHART_TYPE"), messages, messageBuffer); //$NON-NLS-1$ //$NON-NLS-2$ content = messageBuffer.toString(); result = false; } if (!result) { outputStream.append(content); return result; } int chartType = JFreeChartEngine.getChartType(chartTypeStr); AbstractJFreeChartComponent chartComponent = null; try { // Some charts are determined by the dataset that is passed in; check these first... if (datasetType.equalsIgnoreCase(ChartDefinition.TIME_SERIES_COLLECTION_STR)) { chartComponent = new TimeSeriesCollectionChartComponent(chartType, definitionPath, width, height, urlFactory, messages); } else if (datasetType.equalsIgnoreCase(ChartDefinition.XY_SERIES_COLLECTION_STR)) { chartComponent = new XYSeriesCollectionChartComponent(chartType, definitionPath, width, height, urlFactory, messages); } else if (datasetType.equalsIgnoreCase(ChartDefinition.XYZ_SERIES_COLLECTION_STR)) { chartComponent = new XYZSeriesCollectionChartComponent(chartType, definitionPath, width, height, urlFactory, messages); } // Didn't find a dataset, so try to create the component based on chart type. if (chartComponent == null) { switch (chartType) { case JFreeChartEngine.BAR_CHART_TYPE: case JFreeChartEngine.AREA_CHART_TYPE: case JFreeChartEngine.BAR_LINE_CHART_TYPE: case JFreeChartEngine.LINE_CHART_TYPE: case JFreeChartEngine.DIFFERENCE_CHART_TYPE: case JFreeChartEngine.DOT_CHART_TYPE: case JFreeChartEngine.STEP_AREA_CHART_TYPE: case JFreeChartEngine.STEP_CHART_TYPE: case JFreeChartEngine.PIE_GRID_CHART_TYPE: chartComponent = new CategoryDatasetChartComponent(chartType, definitionPath, width, height, urlFactory, messages); break; case JFreeChartEngine.PIE_CHART_TYPE: chartComponent = new PieDatasetChartComponent(chartType, definitionPath, width, height, urlFactory, messages); break; case JFreeChartEngine.DIAL_CHART_TYPE: chartComponent = new DialChartComponent(chartType, definitionPath, width, height, urlFactory, messages); if (dialValue != null) { Number numericDialValue = DataUtilities.toNumber(dialValue, LocaleHelper.getCurrencyFormat(), LocaleHelper.getNumberFormat()); ((DialChartComponent) chartComponent).setValue(numericDialValue.doubleValue()); } break; case JFreeChartEngine.BUBBLE_CHART_TYPE: chartComponent = new XYZSeriesCollectionChartComponent(chartType, definitionPath, width, height, urlFactory, messages); break; case JFreeChartEngine.UNDEFINED_CHART_TYPE: default: // Unsupported chart type, bail out logger.error(Messages.getInstance().getString( "ChartHelper.ERROR_0003_INVALID_CHART_TYPE", chartTypeStr, Integer.toString(chartType))); //$NON-NLS-1$ PentahoSystem.get(IMessageFormatter.class, userSession).formatErrorMessage( "text/html", Messages.getInstance().getString("ChartHelper.ERROR_0003_INVALID_CHART_TYPE", //$NON-NLS-1$ //$NON-NLS-2$ chartTypeStr, Integer.toString(chartType)), messages, messageBuffer); content = messageBuffer.toString(); result = false; } } if (result && (chartComponent != null)) { try { chartComponent.setLoggingLevel(logger.getLoggingLevel()); chartComponent.validate(userSession, null); chartComponent.setDataAction(definitionPath); chartComponent.setUrlTemplate(urlDrillTemplate); String seriesName = parameterProvider.getStringParameter("series-name", null); //$NON-NLS-1$ if (chartComponent instanceof CategoryDatasetChartComponent) { ((CategoryDatasetChartComponent) chartComponent).setSeriesName(seriesName); } // WARNING!!! This is an atypical way to access data for the chart... these parameters and their // usage are undocumented, and only left in here to support older solutions that may be using them. // *************** START QUESTIONABLE CODE ******************************************************** String connectionName = parameterProvider.getStringParameter("connection", null); //$NON-NLS-1$ String query = parameterProvider.getStringParameter("query", null); //$NON-NLS-1$ String dataAction = parameterProvider.getStringParameter("data-process", null); //$NON-NLS-1$ IPentahoConnection connection = null; try { chartComponent.setParamName(innerParam); chartComponent.setParameterProvider(IParameterProvider.SCOPE_REQUEST, parameterProvider); if ((connectionName != null) && (query != null)) { // connection = new SQLConnection(connectionName, logger) // TODO support non-SQL data sources. Much easier now using the factory connection = PentahoConnectionFactory.getConnection(IPentahoConnection.SQL_DATASOURCE, connectionName, userSession, logger); try { query = TemplateUtil.applyTemplate(query, TemplateUtil.parametersToProperties(parameterProvider), null); IPentahoResultSet results = connection.executeQuery(query); chartComponent.setValues(results); } finally { } chartComponent.setUrlTemplate(urlDrillTemplate); if (outerParams != null) { StringTokenizer tokenizer = new StringTokenizer(outerParams, ";"); //$NON-NLS-1$ while (tokenizer.hasMoreTokens()) { chartComponent.addOuterParamName(tokenizer.nextToken()); } } } else if (dataAction != null) { ActionInfo actionInfo = ActionInfo.parseActionString(dataAction); if (actionInfo != null) { chartComponent.setDataAction(actionInfo.getSolutionName(), actionInfo.getPath(), actionInfo .getActionName(), "rule-result"); //$NON-NLS-1$ } } // ***************** END QUESTIONABLE CODE ******************************************************** content = chartComponent.getContent("text/html"); //$NON-NLS-1$ } finally { if (connection != null) { connection.close(); } } } catch (Throwable e) { logger.error(Messages.getInstance().getErrorString("Widget.ERROR_0001_COULD_NOT_CREATE_WIDGET"), e); //$NON-NLS-1$ } } // end of if(result) try { if (content == null) { PentahoSystem .get(IMessageFormatter.class, userSession) .formatErrorMessage( "text/html", Messages.getInstance().getErrorString("Widget.ERROR_0001_COULD_NOT_CREATE_WIDGET"), messages, messageBuffer); //$NON-NLS-1$ //$NON-NLS-2$ content = messageBuffer.toString(); result = false; } outputStream.append(content); } catch (Exception e) { logger.error(Messages.getInstance().getErrorString("Widget.ERROR_0001_COULD_NOT_CREATE_WIDGET"), e); //$NON-NLS-1$ } } finally { if (chartComponent != null) { chartComponent.dispose(); } } return result; } /** * doPieChart generates the images and html necessary to render pie charts. * It provides a simple wrapper around the class * org.pentaho.ui.component.charting.PieDatasetChartComponent * * @param solutionName the solution name * @param actionPath the action path * @param chartName the xml file describing the chart * @param parameterProvider the collection of parameters to customize the chart * @param outputStream the output string buffer for the content * @param userSession the user session object * @param messages a collection to store error and logging messages * @param logger logging object * * @return true if successful * @deprecated use doChart instead */ @Deprecated public static boolean doPieChart(final String solutionName, final String actionPath, final String chartName, final IParameterProvider parameterProvider, final StringBuffer outputStream, final IPentahoSession userSession, final ArrayList messages, final ILogger logger) { boolean result = true; String outerParams = parameterProvider.getStringParameter("outer-params", null); //$NON-NLS-1$ String innerParam = parameterProvider.getStringParameter("inner-param", null); //$NON-NLS-1$ String urlDrillTemplate = parameterProvider.getStringParameter("drill-url", null); //$NON-NLS-1$ String imageUrl = parameterProvider.getStringParameter("image-url", null); //$NON-NLS-1$ if (imageUrl == null) { imageUrl = PentahoSystem.getApplicationContext().getBaseUrl(); } if (urlDrillTemplate == null) { urlDrillTemplate = ""; //$NON-NLS-1$ } int width = (int) parameterProvider.getLongParameter("image-width", 150); //$NON-NLS-1$ int height = (int) parameterProvider.getLongParameter("image-height", 150); //$NON-NLS-1$ SimpleUrlFactory urlFactory = new SimpleUrlFactory(urlDrillTemplate); PieDatasetChartComponent chartComponent = null; try { String chartDefinitionStr = solutionName + File.separator + actionPath + File.separator + chartName; chartComponent = new PieDatasetChartComponent(JFreeChartEngine.PIE_CHART_TYPE, chartDefinitionStr, width, height, urlFactory, messages); if (logger != null) { chartComponent.setLoggingLevel(logger.getLoggingLevel()); } chartComponent.validate(userSession, null); chartComponent.setUrlTemplate(urlDrillTemplate); if (outerParams != null) { StringTokenizer tokenizer = new StringTokenizer(outerParams, ";"); //$NON-NLS-1$ while (tokenizer.hasMoreTokens()) { chartComponent.addOuterParamName(tokenizer.nextToken()); } } chartComponent.setParamName(innerParam); chartComponent.setDataAction(chartDefinitionStr); chartComponent.setParameterProvider(IParameterProvider.SCOPE_REQUEST, parameterProvider); String content = chartComponent.getContent("text/html"); //$NON-NLS-1$ if ((content == null) || content.equals("")) { //$NON-NLS-1$ content = " "; //$NON-NLS-1$ } if (content == null) { StringBuffer buffer = new StringBuffer(); PentahoSystem.get(IMessageFormatter.class, userSession).formatErrorMessage( "text/html", Messages.getInstance().getString("Widget.ERROR_0001_COULD_NOT_CREATE_WIDGET"), messages, buffer); //$NON-NLS-1$ //$NON-NLS-2$ content = buffer.toString(); result = false; } outputStream.append(content); } finally { if (chartComponent != null) { chartComponent.dispose(); } } return result; } /** * doDial generates the images and html necessary to render dials. It * provides a simple wrapper around the class * org.pentaho.ui.component.DashboardWidgetComponent * * @param solutionName the solution name * @param actionPath the action path * @param chartName the xml file describing the chart * @param parameterProvider the collection of parameters to customize the chart * @param outputStream the output string buffer for the content * @param userSession the user session object * @param messages a collection to store error and logging messages * @param logger logging object * * @return true if successful * @deprecated use doChart() instead */ @Deprecated public static boolean doDial(final String solutionName, final String actionPath, final String chartName, final IParameterProvider parameterProvider, final StringBuffer outputStream, final IPentahoSession userSession, final ArrayList messages, final ILogger logger) { boolean result = true; String linkUrl = parameterProvider.getStringParameter("drill-url", null); //$NON-NLS-1$ String imageUrl = parameterProvider.getStringParameter("image-url", null); //$NON-NLS-1$ if (imageUrl == null) { imageUrl = PentahoSystem.getApplicationContext().getBaseUrl(); } if (linkUrl == null) { linkUrl = ""; //$NON-NLS-1$ } int width = (int) parameterProvider.getLongParameter("image-width", 150); //$NON-NLS-1$ int height = (int) parameterProvider.getLongParameter("image-height", 150); //$NON-NLS-1$ SimpleUrlFactory urlFactory = new SimpleUrlFactory(linkUrl); DashboardWidgetComponent widget = null; try { widget = new DashboardWidgetComponent(DashboardWidgetComponent.TYPE_DIAL, solutionName + File.separator + actionPath + File.separator + chartName, width, height, urlFactory, messages); if (logger != null) { widget.setLoggingLevel(logger.getLoggingLevel()); } widget.validate(userSession, null); widget.setParameterProvider(IParameterProvider.SCOPE_REQUEST, parameterProvider); String value = parameterProvider.getStringParameter("value", "0"); //$NON-NLS-1$//$NON-NLS-2$ Number numericValue = DataUtilities.toNumber(value, LocaleHelper.getCurrencyFormat(), LocaleHelper .getNumberFormat()); widget.setValue(numericValue.doubleValue()); String title = parameterProvider.getStringParameter("title", ""); //$NON-NLS-1$ //$NON-NLS-2$ widget.setTitle(title); String content = widget.getContent("text/html"); //$NON-NLS-1$ if (content == null) { StringBuffer buffer = new StringBuffer(); PentahoSystem.get(IMessageFormatter.class, userSession).formatErrorMessage( "text/html", Messages.getInstance().getString("Widget.ERROR_0001_COULD_NOT_CREATE_WIDGET"), messages, buffer); //$NON-NLS-1$ //$NON-NLS-2$ content = buffer.toString(); result = false; } if ((content == null) || content.equals("")) { //$NON-NLS-1$ content = " "; //$NON-NLS-1$ } outputStream.append(content); } finally { if (widget != null) { widget.dispose(); } } return result; } }