001: /*
002: * Copyright 2006 Pentaho Corporation. All rights reserved.
003: * This software was developed by Pentaho Corporation and is provided under the terms
004: * of the Mozilla Public License, Version 1.1, or any later version. You may not use
005: * this file except in compliance with the license. If you need a copy of the license,
006: * please go to http://www.mozilla.org/MPL/MPL-1.1.txt. The Original Code is the Pentaho
007: * BI Platform. The Initial Developer is Pentaho Corporation.
008: *
009: * Software distributed under the Mozilla Public License is distributed on an "AS IS"
010: * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. Please refer to
011: * the license for the specific language governing your rights and limitations.
012: *
013: * @created Aug 23, 2005
014: * @author James Dixon
015: */
016:
017: package org.pentaho.ui.component;
018:
019: import java.io.File;
020: import java.io.IOException;
021: import java.io.OutputStream;
022: import java.io.PrintWriter;
023: import java.io.StringWriter;
024: import java.util.ArrayList;
025: import java.util.HashMap;
026: import java.util.Iterator;
027: import java.util.List;
028: import org.apache.commons.logging.Log;
029: import org.apache.commons.logging.LogFactory;
030: import org.dom4j.Document;
031: import org.dom4j.DocumentHelper;
032: import org.dom4j.Element;
033: import org.dom4j.Node;
034: import org.pentaho.commons.connection.IPentahoMetaData;
035: import org.pentaho.commons.connection.IPentahoResultSet;
036: import org.pentaho.plugin.jfreechart.DialWidgetDefinition;
037: import org.pentaho.plugin.jfreechart.WidgetDefinition;
038: import org.pentaho.core.runtime.IActionParameter;
039: import org.pentaho.core.runtime.IRuntimeContext;
040: import org.pentaho.core.solution.ActionResource;
041: import org.pentaho.core.solution.IActionResource;
042: import org.pentaho.core.solution.ISolutionEngine;
043: import org.pentaho.core.solution.SimpleOutputHandler;
044: import org.pentaho.core.system.PentahoSystem;
045: import org.pentaho.core.ui.IPentahoUrlFactory;
046: import org.pentaho.plugin.jfreechart.JFreeChartEngine;
047: import org.pentaho.core.util.TemplateUtil;
048: import org.pentaho.core.util.XmlHelper;
049: import org.pentaho.ui.XmlComponent;
050: import org.pentaho.messages.Messages;
051: import org.pentaho.util.logging.ILogger;
052:
053: public class WidgetGridComponent extends XmlComponent {
054:
055: /**
056: *
057: */
058: private static final long serialVersionUID = -3952161695550067971L;
059:
060: private String definitionPath;
061:
062: private int widgetWidth;
063:
064: private int widgetHeight;
065:
066: private String solution = null;
067:
068: private String actionPath = null;
069:
070: private String actionName = null;
071:
072: private String valueItem = null;
073:
074: private String nameItem = null;
075:
076: private int columns = 0;
077:
078: private String instanceId = null;
079:
080: private String actionOutput = null;
081:
082: private String urlTemplate = null;
083:
084: private String style = null;
085:
086: private IRuntimeContext context;
087:
088: private static final Log logger = LogFactory
089: .getLog(WidgetGridComponent.class);
090:
091: public Log getLogger() {
092: return logger;
093: }
094:
095: /**
096: * Creates a WidgetGrid
097: * <p>
098: * After creating an instance of this class <CODE>validate</CODE> should
099: * be called.
100: *
101: * @param type
102: * The type of the widget, currently only TYPE_DIAL is supported
103: * @param definitionPath
104: * The path and name of the XML definition of the dial
105: * @param widgetWidth
106: * The width of the image to be created
107: * @param widgetHeight
108: * The height of the image to be created
109: * @param urlFactory
110: * The urlFactory for the content
111: * @param messages
112: * The messages list for any logger messages
113: */
114: public WidgetGridComponent(String definitionPath,
115: IPentahoUrlFactory urlFactory, List messages) {
116: super (urlFactory, messages, null);
117: this .definitionPath = definitionPath;
118: PentahoSystem.ActionInfo info = PentahoSystem
119: .parseActionString(definitionPath);
120: if (info != null) {
121: setSourcePath(info.getSolutionName() + File.separator
122: + info.getPath());
123: }
124: setXsl("text/html", "DialWidget.xsl"); //$NON-NLS-1$ //$NON-NLS-2$
125: }
126:
127: /**
128: * Sets the width (in pixels) of the widget images that will be created
129: *
130: * @param widgetWidth
131: */
132: public void setWidgetWidth(int widgetWidth) {
133: this .widgetWidth = widgetWidth;
134: }
135:
136: /**
137: * Sets the height (in pixels) of the widget images that will be created
138: *
139: * @param widgetHeight
140: */
141: public void setWidgetHeight(int widgetHeight) {
142: this .widgetHeight = widgetHeight;
143: }
144:
145: /**
146: * Sets the number of widgets that will be dispayed in a row before another
147: * row of widgets is created
148: *
149: * @param instanceId
150: */
151: public void setColumns(int columns) {
152: this .columns = columns;
153: }
154:
155: /**
156: * Sets the instance id for this execution
157: *
158: * @param instanceId
159: * The instance id of the parent object or process
160: */
161: public void setInstanceId(String instanceId) {
162: this .instanceId = instanceId;
163: }
164:
165: public boolean setDataAction(String widgetGridDataDefinition) {
166: ActionResource resource = new ActionResource(
167: "", IActionResource.SOLUTION_FILE_RESOURCE, "text/xml", //$NON-NLS-1$ //$NON-NLS-2$
168: widgetGridDataDefinition);
169: try {
170: Document dataActionDocument = PentahoSystem
171: .getSolutionRepository(getSession())
172: .getResourceAsDocument(resource);
173: if (dataActionDocument == null) {
174: return false;
175: }
176: Node dataNode = dataActionDocument
177: .selectSingleNode("widgetgrid/data"); //$NON-NLS-1$
178: solution = XmlHelper.getNodeText("data-solution", dataNode); //$NON-NLS-1$
179: actionPath = XmlHelper.getNodeText("data-path", dataNode); //$NON-NLS-1$
180: actionName = XmlHelper.getNodeText("data-action", dataNode); //$NON-NLS-1$
181: actionOutput = XmlHelper.getNodeText(
182: "data-output", dataNode); //$NON-NLS-1$
183: valueItem = XmlHelper.getNodeText("data-value", dataNode); //$NON-NLS-1$
184: nameItem = XmlHelper.getNodeText("data-name", dataNode); //$NON-NLS-1$
185: widgetWidth = (int) XmlHelper.getNodeText(
186: "widgetgrid/width", dataActionDocument, 125); //$NON-NLS-1$
187: widgetHeight = (int) XmlHelper.getNodeText(
188: "widgetgrid/height", dataActionDocument, 125); //$NON-NLS-1$
189: columns = (int) XmlHelper.getNodeText(
190: "widgetgrid/columns", dataActionDocument, 2); //$NON-NLS-1$
191: style = XmlHelper.getNodeText(
192: "widgetgrid/style", dataActionDocument); //$NON-NLS-1$
193: } catch (Exception e) {
194: error(
195: Messages
196: .getErrorString(
197: "WidgetGrid.ERROR_0003_DEFINITION_NOT_VALID", widgetGridDataDefinition), e); //$NON-NLS-1$
198: return false;
199: }
200: return true;
201: }
202:
203: /**
204: * Sets the action to be executed to get the data for the widgets
205: *
206: * @param solution
207: * @param actionPath
208: * @param actionName
209: * @param actionOutput
210: * @param nameItem
211: * @param valueItem
212: */
213: public void setDataAction(String solution, String actionPath,
214: String actionName, String actionOutput, String nameItem,
215: String valueItem) {
216: this .solution = solution;
217: this .actionPath = actionPath;
218: this .actionName = actionName;
219: this .actionOutput = actionOutput;
220: this .valueItem = valueItem;
221: this .nameItem = nameItem;
222: }
223:
224: public void setDrillUrlTemplate(String urlTemplate) {
225: this .urlTemplate = urlTemplate;
226: }
227:
228: public boolean validate() {
229: return true;
230: }
231:
232: public Document getXmlContent() {
233:
234: // get the data to populate the widgets
235: IPentahoResultSet resultSet = null;
236: if (solution != null) {
237: resultSet = getActionData();
238: } else {
239: // TODO support other methods of getting data
240: }
241:
242: // create the widget to use
243: // load the XML document that defines the dial
244: ActionResource resource = new ActionResource(
245: "", IActionResource.SOLUTION_FILE_RESOURCE, "text/xml", //$NON-NLS-1$ //$NON-NLS-2$
246: definitionPath);
247: Document dialDefinition = null;
248: try {
249: dialDefinition = PentahoSystem.getSolutionRepository(
250: getSession()).getResourceAsDocument(resource);
251: } catch (IOException e) {
252: }
253:
254: // create a dial definition from the XML definition
255: WidgetDefinition widgetDefinition = new DialWidgetDefinition(
256: dialDefinition, 0, widgetWidth, widgetHeight,
257: getSession());
258:
259: return createDials(resultSet, widgetDefinition);
260: }
261:
262: protected Document createDials(IPentahoResultSet resultSet,
263: WidgetDefinition widgetDefinition) {
264:
265: if (resultSet == null) {
266: error(Messages
267: .getErrorString("WidgetGrid.ERROR_0001_NO_RESULTS_FROM_ACTION")); //$NON-NLS-1$
268: return null;
269: }
270:
271: if (valueItem == null) {
272: error(Messages
273: .getErrorString("WidgetGrid.ERROR_0002_NO_VALUE_ITEM")); //$NON-NLS-1$
274: }
275:
276: // Create a document that describes the result
277: Document result = DocumentHelper.createDocument();
278: String baseUrl = PentahoSystem.getApplicationContext()
279: .getBaseUrl();
280: setXslProperty("baseUrl", baseUrl); //$NON-NLS-1$
281: Element root = result.addElement("widgets"); //$NON-NLS-1$
282:
283: IPentahoMetaData metaData = resultSet.getMetaData();
284: // TODO support multiple column headers / row headers
285: // TODO support an iteration across columns for a given row
286:
287: // find the column that we have been told to you
288: Object columnHeaders[][] = metaData.getColumnHeaders();
289: int nameColumnNo = -1;
290: int valueColumnNo = -1;
291: for (int idx = 0; idx < columnHeaders[0].length; idx++) {
292: if (columnHeaders[0][idx].toString().equalsIgnoreCase(
293: nameItem)) {
294: nameColumnNo = idx;
295: }
296: if (columnHeaders[0][idx].toString().equalsIgnoreCase(
297: valueItem)) {
298: valueColumnNo = idx;
299: }
300: }
301:
302: if (nameColumnNo == -1) {
303: // we did not find the specified name column
304: error(Messages
305: .getErrorString(
306: "WidgetGrid.ERROR_0004_NAME_COLUMN_MISSING", nameItem)); //$NON-NLS-1$
307: return null;
308: }
309:
310: if (valueColumnNo == -1) {
311: // we did not find the specified name column
312: error(Messages
313: .getErrorString(
314: "WidgetGrid.ERROR_0005_VALUE_COLUMN_MISSING", valueItem)); //$NON-NLS-1$
315: return null;
316: }
317:
318: double value;
319: String name;
320: Object row[] = resultSet.next();
321: while (row != null) {
322: name = row[nameColumnNo].toString();
323: try {
324: value = Double.parseDouble(row[valueColumnNo]
325: .toString());
326: createDial(value, name, root, widgetDefinition);
327: } catch (Exception e) {
328: }
329:
330: row = resultSet.next();
331: }
332: setXslProperty("urlTarget", "pentaho_popup"); //$NON-NLS-1$ //$NON-NLS-2$
333: setXslProperty("columns", Integer.toString(columns)); //$NON-NLS-1$
334: if (style != null) {
335: setXslProperty("style", style); //$NON-NLS-1$
336: }
337: return result;
338: }
339:
340: protected void createDial(double value, String name, Element root,
341: WidgetDefinition widgetDefinition) {
342:
343: widgetDefinition.setValue(new Double(value));
344:
345: StringWriter stringWriter = new StringWriter();
346: PrintWriter printWriter = new PrintWriter(stringWriter);
347:
348: // TODO get units from somewhere
349: String units = ""; //$NON-NLS-1$
350: String dialName = ""; //$NON-NLS-1$
351: // create temporary file names
352: String solutionDir = "system/tmp/"; //$NON-NLS-1$
353: String fileNamePrefix = "tmp_pie_"; //$NON-NLS-1$
354: String extension = ".png"; //$NON-NLS-1$
355: String fileName = null;
356: String filePathWithoutExtension = null;
357: try {
358: File file = File.createTempFile(fileNamePrefix, extension,
359: new File(PentahoSystem.getApplicationContext()
360: .getFileOutputPath(solutionDir)));
361: file.deleteOnExit();
362: fileName = file.getName();
363: filePathWithoutExtension = solutionDir
364: + fileName.substring(0, fileName.indexOf('.'));
365: } catch (IOException e) {
366: // TODO Auto-generated catch block
367: e.printStackTrace();
368: }
369: JFreeChartEngine.saveChart(widgetDefinition, dialName, units,
370: filePathWithoutExtension, widgetWidth, widgetHeight,
371: JFreeChartEngine.OUTPUT_PNG, printWriter, this );
372:
373: Element widgetNode = root.addElement("widget"); //$NON-NLS-1$
374:
375: widgetNode.addElement("title").setText(name); //$NON-NLS-1$
376: widgetNode.addElement("units").setText(units); //$NON-NLS-1$
377: widgetNode
378: .addElement("width").setText(Integer.toString(widgetWidth)); //$NON-NLS-1$
379: widgetNode
380: .addElement("height").setText(Integer.toString(widgetHeight)); //$NON-NLS-1$
381: Element valueNode = widgetNode.addElement("value");//$NON-NLS-1$
382: valueNode.setText(Double.toString(value));
383: valueNode
384: .addAttribute(
385: "in-image", Boolean.toString(widgetDefinition.getValueFont() != null)); //$NON-NLS-1$
386: root.addElement("image").setText(fileName); //$NON-NLS-1$
387: widgetNode.addElement("image").setText(fileName); //$NON-NLS-1$
388:
389: // apply the current data item name to the URL template
390: String drillUrl = TemplateUtil.applyTemplate(urlTemplate,
391: nameItem, name);
392:
393: // now apply any parameters to the URL template
394: drillUrl = TemplateUtil.applyTemplate(drillUrl, context);
395:
396: widgetNode.addElement("urlDrill").setText(drillUrl); //$NON-NLS-1$
397:
398: }
399:
400: public void dispose() {
401: if (context != null)
402: context.dispose();
403: }
404:
405: protected IPentahoResultSet getActionData() {
406: // create an instance of the solution engine to execute the specified
407: // action
408:
409: ISolutionEngine solutionEngine = PentahoSystem
410: .getSolutionEngineInstance(getSession());
411: solutionEngine.setLoggingLevel(ILogger.DEBUG);
412: solutionEngine.init(getSession());
413:
414: HashMap parameterProviders = getParameterProviders();
415:
416: OutputStream outputStream = null;
417: SimpleOutputHandler outputHandler = null;
418: outputHandler = new SimpleOutputHandler(outputStream, false);
419:
420: ArrayList messages = new ArrayList();
421: String processId = this .getClass().getName();
422: context = solutionEngine.execute(solution, actionPath,
423: actionName, processId, false, true, instanceId, false,
424: parameterProviders, outputHandler, null, urlFactory,
425: messages);
426:
427: if (actionOutput != null) {
428: if (context.getOutputNames().contains(actionOutput)) {
429: IActionParameter output = context
430: .getOutputParameter(actionOutput);
431: IPentahoResultSet results = output
432: .getValueAsResultSet();
433: if (results != null) {
434: results = results.memoryCopy();
435: }
436: return results;
437: } else {
438: // this is an error
439: return null;
440: }
441: } else {
442: // return the first list that we find...
443: Iterator it = context.getOutputNames().iterator();
444: while (it.hasNext()) {
445: IActionParameter output = (IActionParameter) it.next();
446: if (output.getType().equalsIgnoreCase(
447: IActionParameter.TYPE_RESULT_SET)) {
448: IPentahoResultSet results = output
449: .getValueAsResultSet();
450: if (results != null) {
451: results = results.memoryCopy();
452: }
453: return results;
454: }
455: }
456: }
457: return null;
458: }
459:
460: }
|