/*==========================================================================*\ | $Id: CreateTransformedDataSetDialog.java,v 1.1 2010/05/11 15:52:47 aallowat Exp $ |*-------------------------------------------------------------------------*| | Copyright (C) 2006-2008 Virginia Tech | | This file is part of Web-CAT. | | Web-CAT is free software; you can redistribute it and/or modify | it under the terms of the GNU Affero General Public License as published | by the Free Software Foundation; either version 3 of the License, or | (at your option) any later version. | | Web-CAT 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. | | You should have received a copy of the GNU Affero General Public License | along with Web-CAT; if not, see <http://www.gnu.org/licenses/>. \*==========================================================================*/ package org.webcat.oda.designer.transform; import java.io.StringWriter; import java.io.Writer; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.Properties; import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.runtime.RuntimeConstants; import org.apache.velocity.runtime.resource.loader.URLResourceLoader; import org.eclipse.birt.report.model.api.CellHandle; import org.eclipse.birt.report.model.api.ColumnHintHandle; import org.eclipse.birt.report.model.api.DataItemHandle; import org.eclipse.birt.report.model.api.DataSetHandle; import org.eclipse.birt.report.model.api.DataSourceHandle; import org.eclipse.birt.report.model.api.ElementFactory; import org.eclipse.birt.report.model.api.ModuleHandle; import org.eclipse.birt.report.model.api.OdaResultSetColumnHandle; import org.eclipse.birt.report.model.api.PropertyHandle; import org.eclipse.birt.report.model.api.ReportDesignHandle; import org.eclipse.birt.report.model.api.RowHandle; import org.eclipse.birt.report.model.api.ScriptDataSetHandle; import org.eclipse.birt.report.model.api.ScriptDataSourceHandle; import org.eclipse.birt.report.model.api.StructureFactory; import org.eclipse.birt.report.model.api.TableHandle; import org.eclipse.birt.report.model.api.activity.SemanticException; import org.eclipse.birt.report.model.api.command.ContentException; import org.eclipse.birt.report.model.api.command.NameException; import org.eclipse.birt.report.model.api.elements.structures.ColumnHint; import org.eclipse.birt.report.model.api.elements.structures.ComputedColumn; import org.eclipse.birt.report.model.api.elements.structures.HideRule; import org.eclipse.birt.report.model.api.elements.structures.ResultSetColumn; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.Path; import org.eclipse.jface.dialogs.TitleAreaDialog; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; import org.webcat.oda.designer.DesignerActivator; // ------------------------------------------------------------------------ /** * A dialog that lets the user derive a scripted data set that transforms data * from another data set. * * @author Tony Allevato (Virginia Tech Computer Science) * @version $Id: CreateTransformedDataSetDialog.java,v 1.1 2010/05/11 15:52:47 aallowat Exp $ */ public class CreateTransformedDataSetDialog extends TitleAreaDialog { //~ Constructor ........................................................... // ---------------------------------------------------------- /** * Creates a new Create Transformed Data Set dialog that operates on the * specified report model. * * @param parentShell * the Shell that will own the dialog * @param model * the report model to operate on */ public CreateTransformedDataSetDialog(Shell parentShell, ModuleHandle model) { super(parentShell); int style = getShellStyle() | SWT.RESIZE; setShellStyle(style); this.model = model; initializeVelocity(); } private void initializeVelocity() { velocity = new VelocityEngine(); try { URL url = FileLocator.find( DesignerActivator.getDefault().getBundle(), new Path("velocity"), null); url = FileLocator.resolve(url); Properties props = new Properties(); props.setProperty(RuntimeConstants.RESOURCE_LOADER, "url"); props.setProperty("url.resource.loader.description", "Velocity URL Resource Loader"); props.setProperty("url.resource.loader.class", URLResourceLoader.class.getName()); props.setProperty("url.resource.loader.root", url.toString()); velocity.init(props); } catch (Exception e) { e.printStackTrace(); } } // ---------------------------------------------------------- @Override protected Control createContents(Composite parent) { Control control = super.createContents(parent); getShell().setText("Create Transformed Data Set"); setTitle("Create Transformed Data Set"); setMessage("Use the options below to create a new scripted data set that derives its data from an existing data set."); return control; } // ---------------------------------------------------------- @Override protected Control createDialogArea(Composite parent) { Composite composite = (Composite) super.createDialogArea(parent); Composite panel = new Composite(composite, SWT.NONE); GridLayout layout = new GridLayout(2, false); layout.marginWidth = 10; layout.marginHeight = 10; panel.setLayout(layout); panel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); Label label; GridData gd; GridData gdSep = new GridData(SWT.FILL, SWT.CENTER, true, false); gdSep.horizontalSpan = 2; GridData gdWideLabel = new GridData(SWT.FILL, SWT.TOP, true, false); gdWideLabel.horizontalSpan = 2; label = new Label(panel, SWT.NONE); label.setText("New data set name:"); dataSetNameField = new Text(panel, SWT.BORDER); gd = new GridData(SWT.FILL, SWT.CENTER, true, false); dataSetNameField.setLayoutData(gd); label = new Label(panel, SWT.NONE); label.setText("Data set to transform:"); sourceDataSetField = new Combo(panel, SWT.DROP_DOWN | SWT.READ_ONLY); gd = new GridData(SWT.FILL, SWT.CENTER, true, false); sourceDataSetField.setLayoutData(gd); sourceDataSetField.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { sourceDataSetFieldChanged(); } }); new Label(panel, SWT.SEPARATOR | SWT.HORIZONTAL).setLayoutData(gdSep); Composite subPanel = new Composite(panel, SWT.NONE); layout = new GridLayout(2, false); layout.marginWidth = 0; layout.marginHeight = 0; subPanel.setLayout(layout); subPanel.setLayoutData(gdWideLabel); existingDataSourceButton = new Button(subPanel, SWT.RADIO); existingDataSourceButton .setText("Use an existing scripted data source:"); existingDataSourceField = new Combo(subPanel, SWT.DROP_DOWN | SWT.READ_ONLY); gd = new GridData(SWT.FILL, SWT.CENTER, true, false); existingDataSourceField.setLayoutData(gd); newDataSourceButton = new Button(subPanel, SWT.RADIO); newDataSourceButton.setText("Create a new scripted data source named:"); newDataSourceField = new Text(subPanel, SWT.BORDER); gd = new GridData(SWT.FILL, SWT.CENTER, true, false); newDataSourceField.setLayoutData(gd); new Label(panel, SWT.SEPARATOR | SWT.HORIZONTAL).setLayoutData(gdSep); label = new Label(panel, SWT.NONE); label.setText("Take one row for each"); groupingKeyField = new Combo(panel, SWT.DROP_DOWN | SWT.READ_ONLY); gd = new GridData(SWT.FILL, SWT.CENTER, true, false); groupingKeyField.setLayoutData(gd); label = new Label(panel, SWT.NONE); label.setText("that has the"); aggregationField = new Combo(panel, SWT.DROP_DOWN | SWT.READ_ONLY); gd = new GridData(SWT.FILL, SWT.CENTER, true, false); aggregationField.setLayoutData(gd); label = new Label(panel, SWT.NONE); label.setText("value for the column"); aggregationKeyField = new Combo(panel, SWT.DROP_DOWN | SWT.READ_ONLY); gd = new GridData(SWT.FILL, SWT.CENTER, true, false); aggregationKeyField.setLayoutData(gd); initializeFields(); return composite; } // ---------------------------------------------------------- @Override protected void okPressed() { try { populateModel(); } catch (Exception e) { // TODO handle properly e.printStackTrace(); } super.okPressed(); } //~ Private Methods ....................................................... private void sourceDataSetFieldChanged() { DataSetHandle sourceDataSet = (DataSetHandle) model.getDataSets().get( sourceDataSetField.getSelectionIndex()); PropertyHandle sourceColumns = sourceDataSet .getPropertyHandle(DataSetHandle.RESULT_SET_PROP); int numColumns = sourceColumns.getIntValue(); groupingKeyField.removeAll(); aggregationKeyField.removeAll(); for (int i = 0; i < numColumns; i++) { OdaResultSetColumnHandle sourceColumn = (OdaResultSetColumnHandle) sourceColumns .get(i); groupingKeyField.add(sourceColumn.getColumnName()); aggregationKeyField.add(sourceColumn.getColumnName()); } } // ---------------------------------------------------------- /** * Initialize controls with values from the report model. */ private void initializeFields() { for (int i = 0; i < model.getDataSets().getCount(); i++) { DataSetHandle dataSet = (DataSetHandle) model.getDataSets().get(i); String name = dataSet.getName() + " (" + dataSet.getDataSourceName() + ")"; sourceDataSetField.add(name); } int numScriptedDataSources = 0; scriptedDataSources = new ArrayList<ScriptDataSourceHandle>(); for (int i = 0; i < model.getDataSources().getCount(); i++) { DataSourceHandle dataSource = (DataSourceHandle) model .getDataSources().get(i); if (dataSource instanceof ScriptDataSourceHandle) { scriptedDataSources.add((ScriptDataSourceHandle) dataSource); existingDataSourceField.add(dataSource.getName()); numScriptedDataSources++; } } if (numScriptedDataSources > 0) { existingDataSourceButton.setSelection(true); existingDataSourceField.select(0); } else { existingDataSourceButton.setEnabled(false); existingDataSourceField.setEnabled(false); newDataSourceButton.setSelection(true); } aggregationField.add("maximum"); aggregationField.add("minimum"); } // ---------------------------------------------------------- private void populateModel() throws NameException, ContentException, SemanticException { ElementFactory elementFactory = model.getElementFactory(); DataSetHandle sourceDataSet = (DataSetHandle) model.getDataSets().get( sourceDataSetField.getSelectionIndex()); // Create the new scripted data source if necessary, or get the // existing one. ScriptDataSourceHandle scriptDataSource; if (newDataSourceButton.getSelection()) { String dataSourceName = newDataSourceField.getText(); scriptDataSource = elementFactory .newScriptDataSource(dataSourceName); model.getDataSources().add(scriptDataSource); } else { int index = existingDataSourceField.getSelectionIndex(); scriptDataSource = scriptedDataSources.get(index); } String varName = sanitizeDataSetName(sourceDataSet) + "_results"; Properties params = new Properties(); params.setProperty(PROP_RESULTS_VARIABLE_NAME_KEY, varName); params.setProperty(PROP_GROUPING_KEY, groupingKeyField.getText()); params.setProperty(PROP_AGGREGATION, aggregationField.getText()); params.setProperty(PROP_AGGREGATION_KEY, aggregationKeyField.getText()); // Add script code to the source data set to store the data in // intermediate JavaScript objects. addSourceDataSetScripts(sourceDataSet, params); // Create the new scripted data set. ScriptDataSetHandle newDataSet = createScriptedDataSet(elementFactory, scriptDataSource, params); copyDataSetColumns(elementFactory, sourceDataSet, newDataSet); // Add a hidden table to the top of the report that references the // source data set. // // TODO Consider adding a new report item to the designer that acts as // a non-rendered data set runner createHiddenTable(elementFactory, sourceDataSet); } // ---------------------------------------------------------- /** * Creates a hidden table at the top of the report body that forces data * from the specified data set to be generated (and the data set's scripts * to be executed). * * @param elementFactory * the element factory to use * @param sourceDataSet * the source data set for the table * * @throws SemanticException * @throws ContentException * @throws NameException */ private void createHiddenTable(ElementFactory elementFactory, DataSetHandle sourceDataSet) throws SemanticException, ContentException, NameException { String tableName = "Hidden Table to Generate Data For " + sourceDataSet.getName(); // Get the list of columns from the source data set. PropertyHandle columns = sourceDataSet .getPropertyHandle(DataSetHandle.RESULT_SET_PROP); int numColumns = columns.getIntValue(); // Create the table item. TableHandle hiddenTable = elementFactory.newTableItem(tableName, numColumns, 0, 1, 0); hiddenTable.setDataSet(sourceDataSet); // Create the data bindings for each column in the table. PropertyHandle tableBindings = hiddenTable.getColumnBindings(); for (int i = 0; i < numColumns; i++) { OdaResultSetColumnHandle column = (OdaResultSetColumnHandle) columns .get(i); ComputedColumn binding = StructureFactory.createComputedColumn(); binding.setDataType(column.getDataType()); binding.setName(column.getColumnName()); binding.setExpression("dataSetRow[\"" + column.getColumnName() + "\"]"); tableBindings.addItem(binding); } // Create the cells and data items for the table. RowHandle detailRow = (RowHandle) hiddenTable.getDetail().get(0); for (int i = 0; i < numColumns; i++) { OdaResultSetColumnHandle column = (OdaResultSetColumnHandle) columns .get(i); CellHandle cell = (CellHandle) detailRow.getCells().get(i); DataItemHandle dataItem = elementFactory.newDataItem(null); dataItem.setResultSetColumn(column.getColumnName()); cell.getContent().add(dataItem); } // Set the table to be hidden in all rendering formats. HideRule hideRule = StructureFactory.createHideRule(); hideRule.setFormat("all"); hideRule.setExpression("true"); hiddenTable.getPropertyHandle(TableHandle.VISIBILITY_PROP).addItem( hideRule); // Add the table to the top of the report body. ((ReportDesignHandle) model).getBody().add(hiddenTable, 0); } // ---------------------------------------------------------- /** * Creates a valid JavaScript identifier from the name of a data set. * * @param dataSet * the data set whose name should be sanitized * * @return the sanitized name of the data set */ private String sanitizeDataSetName(DataSetHandle dataSet) { String name = dataSet.getName(); StringBuilder buffer = new StringBuilder(name.length()); if (!Character.isJavaIdentifierStart(name.charAt(0))) { buffer.append('_'); } for (int i = 0; i < name.length(); i++) { char ch = name.charAt(i); if (Character.isJavaIdentifierPart(ch)) { buffer.append(ch); } else { buffer.append('_'); } } return buffer.toString(); } // ---------------------------------------------------------- private void addSourceDataSetScripts(DataSetHandle sourceDataSet, Properties params) throws SemanticException { // String groupingKey = params.getProperty(PROP_GROUPING_KEY); // String aggregation = params.getProperty(PROP_AGGREGATION); // String aggKey = params.getProperty(PROP_AGGREGATION_KEY); /* StringBuilder buffer = new StringBuilder(256); buffer.append(resultsName + " = new java.util.Hashtable();\n");*/ StringWriter writer = new StringWriter(); mergeTemplate("dsxform_source_afterOpen.jstemplate", params, writer); sourceDataSet.setAfterOpen(writer.toString()); /* buffer = new StringBuilder(512); buffer.append("var key = row[\"" + groupingKey + "\"];\n"); buffer.append("var put = false;\n\n"); buffer.append("if (" + resultsName + ".containsKey(key))\n"); buffer.append("{\n"); buffer.append(" var current = " + resultsName + ".get(key);\n"); buffer.append(" // Modify this expression to alter the decision\n"); buffer.append(" // about whether the row is included.\n"); if ("maximum".equals(aggregation)) { buffer.append(" put = (row[\"" + aggKey + "\"] > current[\"" + aggKey + "\"]);\n"); } else if ("minimum".equals(aggregation)) { buffer.append(" put = (row[\"" + aggKey + "\"] < current[\"" + aggKey + "\"]);\n"); } buffer.append("}\n"); buffer.append("else\n"); buffer.append("{\n"); buffer.append(" put = true;\n"); buffer.append("}\n\n"); buffer .append("// Add the row to the table that will be carried into the\n"); buffer.append("// scripted data set.\n"); buffer.append("if (put)\n"); buffer.append("{\n"); buffer.append(" var bundle = new Object();\n"); buffer.append(" for (var i = 0; i < row.columnDefns.length; i++)\n"); buffer.append(" {\n"); buffer.append(" var name = row.columnDefns[i].name;\n"); buffer.append(" bundle[name] = row[name];\n"); buffer.append(" }\n\n"); buffer.append(" " + resultsName + ".put(key, bundle);\n"); buffer.append("}\n");*/ writer = new StringWriter(); mergeTemplate("dsxform_source_onFetch.jstemplate", params, writer); sourceDataSet.setOnFetch(writer.toString()); } // ---------------------------------------------------------- /** * Creates a scripted data set and the associated scripts. * * @param elementFactory * the factory to use to create design elements * @param scriptDataSource * the data source under which the data set will be created * * @return the new scripted data set * * @throws SemanticException * @throws ContentException * @throws NameException */ private ScriptDataSetHandle createScriptedDataSet( ElementFactory elementFactory, ScriptDataSourceHandle scriptDataSource, Properties params) throws SemanticException, ContentException, NameException { String dataSetName = dataSetNameField.getText(); ScriptDataSetHandle newDataSet = elementFactory .newScriptDataSet(dataSetName); newDataSet.setDataSource(scriptDataSource.getName()); model.getDataSets().add(newDataSet); // Build the JavaScript code for the data set. StringWriter writer = new StringWriter(); mergeTemplate("dsxform_derived_open.jstemplate", params, writer); newDataSet.setOpen(writer.toString()); writer = new StringWriter(); mergeTemplate("dsxform_derived_fetch.jstemplate", params, writer); newDataSet.setFetch(writer.toString()); /* StringBuilder buffer = new StringBuilder(); buffer .append("iterator = " + resultsName + ".entrySet().iterator();\n"); newDataSet.setOpen(buffer.toString()); buffer = new StringBuilder(); buffer.append("if (iterator.hasNext())\n"); buffer.append("{\n"); buffer.append(" var entry = iterator.next();\n"); buffer.append(" var columnBundle = entry.getValue();\n"); buffer.append(" for (var column in columnBundle)\n"); buffer.append(" row[column] = columnBundle[column];\n\n"); buffer.append(" return true;\n"); buffer.append("}\n"); buffer.append("else\n"); buffer.append("{\n"); buffer.append(" return false;\n"); buffer.append("}\n"); newDataSet.setFetch(buffer.toString()); */ return newDataSet; } // ---------------------------------------------------------- /** * Copies the data set columns from the source data set to the new scripted * data set. * * @param elementFactory * the element factory to use * @param sourceDataSet * the data set from which columns should be copied * @param scriptDataSet * the data set to which columns should be copied * * @throws SemanticException */ private void copyDataSetColumns(ElementFactory elementFactory, DataSetHandle sourceDataSet, ScriptDataSetHandle scriptDataSet) throws SemanticException { PropertyHandle sourceColumns = sourceDataSet .getPropertyHandle(DataSetHandle.RESULT_SET_PROP); PropertyHandle sourceColumnHints = sourceDataSet .getPropertyHandle(DataSetHandle.COLUMN_HINTS_PROP); PropertyHandle scriptColumns = scriptDataSet .getPropertyHandle(DataSetHandle.RESULT_SET_PROP); PropertyHandle scriptColumnHints = scriptDataSet .getPropertyHandle(DataSetHandle.COLUMN_HINTS_PROP); int numColumns = sourceColumns.getIntValue(); for (int i = 0; i < numColumns; i++) { OdaResultSetColumnHandle sourceColumn = (OdaResultSetColumnHandle) sourceColumns .get(i); ColumnHintHandle sourceHint = (ColumnHintHandle) sourceColumnHints .get(i); ResultSetColumn resultColumn = StructureFactory .createResultSetColumn(); resultColumn.setPosition(i + 1); resultColumn.setColumnName(sourceColumn.getColumnName()); resultColumn.setDataType(sourceColumn.getDataType()); scriptColumns.addItem(resultColumn); ColumnHint hint = StructureFactory.createColumnHint(); hint.setProperty(ColumnHint.COLUMN_NAME_MEMBER, sourceColumn .getColumnName()); hint.setProperty(ColumnHint.ALIAS_MEMBER, sourceHint.getAlias()); hint.setProperty(ColumnHint.DISPLAY_NAME_MEMBER, sourceHint .getDisplayName()); scriptColumnHints.addItem(hint); } } private void mergeTemplate(String name, Properties params, Writer writer) { try { VelocityContext context = new VelocityContext(params); Template template = velocity.getTemplate(name); template.merge(context, writer); } catch (Exception e) { e.printStackTrace(); } } //~ Static/instance variables ............................................. private static final String PROP_RESULTS_VARIABLE_NAME_KEY = "resultsVariableName"; private static final String PROP_GROUPING_KEY = "groupingKey"; private static final String PROP_AGGREGATION = "aggregation"; private static final String PROP_AGGREGATION_KEY = "aggregationKey"; private ModuleHandle model; private List<ScriptDataSourceHandle> scriptedDataSources; private VelocityEngine velocity; private Text dataSetNameField; private Combo sourceDataSetField; private Button existingDataSourceButton; private Combo existingDataSourceField; private Button newDataSourceButton; private Text newDataSourceField; private Combo groupingKeyField; private Combo aggregationField; private Combo aggregationKeyField; }