/*
* 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;
}
}