/* * 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 2008 - 2009 Pentaho Corporation. All rights reserved. * * * Created April 21, 2009 * @author rmansoor */ package org.pentaho.platform.dataaccess.datasource.wizard.controllers; import java.util.ArrayList; import java.util.List; import java.util.Vector; import org.pentaho.metadata.model.Domain; import org.pentaho.metadata.model.SqlPhysicalModel; import org.pentaho.metadata.model.concept.types.AggregationType; import org.pentaho.platform.dataaccess.datasource.DatasourceType; import org.pentaho.platform.dataaccess.datasource.IConnection; import org.pentaho.platform.dataaccess.datasource.beans.BusinessData; import org.pentaho.platform.dataaccess.datasource.beans.SerializedResultSet; import org.pentaho.platform.dataaccess.datasource.utils.ExceptionParser; import org.pentaho.platform.dataaccess.datasource.wizard.DatasourceMessages; import org.pentaho.platform.dataaccess.datasource.wizard.WaitingDialog; import org.pentaho.platform.dataaccess.datasource.wizard.models.Aggregation; import org.pentaho.platform.dataaccess.datasource.wizard.models.DatasourceModel; import org.pentaho.platform.dataaccess.datasource.wizard.models.ModelDataRow; import org.pentaho.platform.dataaccess.datasource.wizard.service.IXulAsyncDatasourceService; import org.pentaho.ui.xul.XulComponent; import org.pentaho.ui.xul.XulException; import org.pentaho.ui.xul.XulServiceCallback; import org.pentaho.ui.xul.binding.Binding; import org.pentaho.ui.xul.binding.BindingConvertor; import org.pentaho.ui.xul.binding.BindingFactory; import org.pentaho.ui.xul.components.XulButton; import org.pentaho.ui.xul.components.XulLabel; import org.pentaho.ui.xul.components.XulTextbox; import org.pentaho.ui.xul.components.XulTreeCell; import org.pentaho.ui.xul.components.XulTreeCol; import org.pentaho.ui.xul.containers.XulDialog; import org.pentaho.ui.xul.containers.XulListbox; import org.pentaho.ui.xul.containers.XulTree; import org.pentaho.ui.xul.containers.XulTreeChildren; import org.pentaho.ui.xul.containers.XulTreeCols; import org.pentaho.ui.xul.containers.XulTreeRow; import org.pentaho.ui.xul.containers.XulVbox; import org.pentaho.ui.xul.impl.AbstractXulEventHandler; import org.pentaho.ui.xul.stereotype.Bindable; import org.pentaho.ui.xul.util.TreeCellEditor; import org.pentaho.ui.xul.util.TreeCellEditorCallback; import org.pentaho.ui.xul.util.TreeCellRenderer; public class RelationalDatasourceController extends AbstractXulEventHandler implements IDatasourceTypeController { public static final int MAX_SAMPLE_DATA_ROWS = 5; public static final int MAX_COL_SIZE = 13; public static final String EMPTY_STRING = ""; //$NON-NLS-1$ public static final String COMMA = ","; //$NON-NLS-1$ private DatasourceMessages datasourceMessages; private XulDialog connectionDialog; private WaitingDialog waitingDialogBox; private XulDialog waitingDialog = null; private XulDialog applyQueryConfirmationDialog = null; private XulLabel waitingDialogLabel = null; private XulDialog previewResultsDialog = null; private IXulAsyncDatasourceService service; private DatasourceModel datasourceModel; BindingFactory bf; XulTree previewResultsTable = null; XulTextbox datasourceName = null; XulListbox connections = null; XulTextbox query = null; XulTreeCols previewResultsTreeCols = null; XulTextbox previewLimit = null; XulButton editConnectionButton = null; XulButton removeConnectionButton = null; XulButton editQueryButton = null; XulButton previewButton = null; private XulDialog errorDialog; private XulDialog successDialog; private XulLabel errorLabel = null; private XulLabel successLabel = null; private XulTree modelDataTable = null; private XulButton applyButton = null; private XulTreeCol columnNameTreeCol = null; private XulTreeCol columnTypeTreeCol = null; //private XulTreeCol columnFormatTreeCol = null;\ XulTree sampleDataTree = null; XulDialog aggregationEditorDialog = null; XulDialog sampleDataDialog = null; CustomAggregateCellEditor aggregationCellEditor = null; CustomSampleDataCellEditor sampleDataCellEditor = null; CustomSampleDataCellRenderer sampleDataCellRenderer = null; //private XulRows rows = null; //private XulGrid grid = null; CustomAggregationCellRenderer aggregationCellRenderer = null; private XulVbox relationalAggregationEditorVbox = null; public RelationalDatasourceController() { } @Bindable public void init() { //rows = (XulRows) document.getElementById("relationalSampleDataRows");//$NON-NLS-1$ //grid = (XulGrid) document.getElementById("relationalSampleDataGrid");//$NON-NLS-1$ relationalAggregationEditorVbox = (XulVbox) document.getElementById("relationalAggregationEditorVbox"); //$NON-NLS-1$ applyButton = (XulButton) document.getElementById("apply"); //$NON-NLS-1$ modelDataTable = (XulTree) document.getElementById("modelDataTable"); sampleDataTree = (XulTree) document.getElementById("relationalSampleDataTable"); aggregationEditorDialog = (XulDialog) document.getElementById("relationalAggregationEditorDialog"); aggregationCellEditor = new CustomAggregateCellEditor(aggregationEditorDialog, datasourceMessages, document, bf); modelDataTable.registerCellEditor("aggregation-cell-editor", aggregationCellEditor); aggregationCellRenderer = new CustomAggregationCellRenderer(); modelDataTable.registerCellRenderer("aggregation-cell-editor", aggregationCellRenderer); sampleDataDialog = (XulDialog) document.getElementById("relationalSampleDataDialog"); sampleDataCellEditor = new CustomSampleDataCellEditor(sampleDataDialog); modelDataTable.registerCellEditor("sample-data-cell-editor", sampleDataCellEditor); sampleDataCellRenderer = new CustomSampleDataCellRenderer(); modelDataTable.registerCellRenderer("sample-data-cell-editor", sampleDataCellRenderer); errorDialog = (XulDialog) document.getElementById("errorDialog"); //$NON-NLS-1$ errorLabel = (XulLabel) document.getElementById("errorLabel");//$NON-NLS-1$ applyQueryConfirmationDialog = (XulDialog) document.getElementById("applyQueryConfirmationDialog"); //$NON-NLS-1$ errorLabel = (XulLabel) document.getElementById("errorLabel");//$NON-NLS-1$ waitingDialog = (XulDialog) document.getElementById("waitingDialog"); //$NON-NLS-1$ waitingDialogLabel = (XulLabel) document.getElementById("waitingDialogLabel");//$NON-NLS-1$ successDialog = (XulDialog) document.getElementById("successDialog"); //$NON-NLS-1$ successLabel = (XulLabel) document.getElementById("successLabel");//$NON-NLS-1$ datasourceName = (XulTextbox) document.getElementById("relationalDatasourceName"); //$NON-NLS-1$ connections = (XulListbox) document.getElementById("connectionList"); //$NON-NLS-1$ query = (XulTextbox) document.getElementById("query"); //$NON-NLS-1$ connectionDialog = (XulDialog) document.getElementById("connectionDialog");//$NON-NLS-1$ previewResultsDialog = (XulDialog) document.getElementById("previewResultsDialog");//$NON-NLS-1$ previewResultsTable = (XulTree) document.getElementById("previewResultsTable"); //$NON-NLS-1$ previewResultsTreeCols = (XulTreeCols) document.getElementById("previewResultsTreeCols"); //$NON-NLS-1$ previewLimit = (XulTextbox) document.getElementById("previewLimit"); //$NON-NLS-1$ editConnectionButton = (XulButton) document.getElementById("editConnection"); //$NON-NLS-1$ removeConnectionButton = (XulButton) document.getElementById("removeConnection"); //$NON-NLS-1$ editQueryButton = (XulButton) document.getElementById("editQuery"); //$NON-NLS-1$ previewButton = (XulButton) document.getElementById("preview"); //$NON-NLS-1$ columnNameTreeCol = (XulTreeCol) document.getElementById("relationalColumnNameTreeCol"); //$NON-NLS-1$ columnTypeTreeCol = (XulTreeCol) document.getElementById("relationalColumnTypeTreeCol"); //$NON-NLS-1$ //columnFormatTreeCol = (XulTreeCol) document.getElementById("relationalColumnFormatTreeCol"); //$NON-NLS-1$ bf.setBindingType(Binding.Type.ONE_WAY); bf.createBinding(datasourceModel.getRelationalModel(), "previewValidated", previewButton, "!disabled");//$NON-NLS-1$ //$NON-NLS-2$ bf.createBinding(datasourceModel.getRelationalModel(), "applyValidated", applyButton, "!disabled");//$NON-NLS-1$ //$NON-NLS-2$ BindingConvertor<String, Boolean> widgetBindingConvertor = new BindingConvertor<String, Boolean>() { @Override public Boolean sourceToTarget(String value) { return !((value == null) || value.length() <= 0); } @Override public String targetToSource(Boolean value) { return null; } }; List<Binding> bindingsThatNeedInitialized = new ArrayList<Binding>(); BindingConvertor<IConnection, Boolean> buttonConvertor = new BindingConvertor<IConnection, Boolean>() { @Override public Boolean sourceToTarget(IConnection value) { return !(value == null); } @Override public IConnection targetToSource(Boolean value) { return null; } }; bf.setBindingType(Binding.Type.ONE_WAY); final Binding domainBinding = bf.createBinding(datasourceModel.getRelationalModel(), "connections", connections, "elements"); //$NON-NLS-1$ //$NON-NLS-2$ bf.createBinding(datasourceModel.getRelationalModel(), "selectedConnection", editConnectionButton, "!disabled", buttonConvertor); //$NON-NLS-1$ //$NON-NLS-2$ bf.createBinding(datasourceModel.getRelationalModel(), "selectedConnection", removeConnectionButton, "!disabled", buttonConvertor); //$NON-NLS-1$ //$NON-NLS-2$ bf.setBindingType(Binding.Type.BI_DIRECTIONAL); bf.createBinding(datasourceModel.getRelationalModel(), "selectedConnection", connections, "selectedIndex", new BindingConvertor<IConnection, Integer>() { //$NON-NLS-1$ //$NON-NLS-2$ @Override public Integer sourceToTarget(IConnection connection) { if (connection != null) { return datasourceModel.getRelationalModel().getConnectionIndex(connection); } else { return -1; } } @Override public IConnection targetToSource(Integer value) { if (value >= 0) { return datasourceModel.getRelationalModel().getConnections().get(value); } else { return null; } } }); bf.createBinding(datasourceModel.getRelationalModel(), "dataRows", modelDataTable, "elements"); bf.setBindingType(Binding.Type.BI_DIRECTIONAL); bf.createBinding(datasourceModel.getRelationalModel(), "previewLimit", previewLimit, "value"); //$NON-NLS-1$ //$NON-NLS-2$ // Not sure if editQuery button is doing much //bf.createBinding(editQueryButton, "!disabled", "removeConnectionButton", "!disabled", buttonConvertor); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ bf.setBindingType(Binding.Type.BI_DIRECTIONAL); bf.createBinding(datasourceModel.getRelationalModel(), "query", query, "value"); //$NON-NLS-1$ //$NON-NLS-2$ try { // Fires the population of the model listbox. This cascades down to the categories and columns. In essence, this // call initializes the entire UI. domainBinding.fireSourceChanged(); } catch (Exception e) { System.out.println(e.getMessage()); e.printStackTrace(); } for(Binding b : bindingsThatNeedInitialized){ try { b.fireSourceChanged(); } catch (Exception e) { System.out.println(e.getMessage()); e.printStackTrace(); } } } public void setBindingFactory(BindingFactory bf) { this.bf = bf; } @Bindable public void setDatasourceModel(DatasourceModel model) { this.datasourceModel = model; } @Bindable public DatasourceModel getDatasourceModel() { return this.datasourceModel; } public String getName() { return "relationalDatasourceController"; } @Bindable public void applyQuery() { if (datasourceModel.getRelationalModel().getBusinessData() != null) { applyQueryConfirmationDialog.show(); } else { generateModel(); } } @Bindable public void generateModel() { query.setDisabled(true); if (applyQueryConfirmationDialog.isVisible()) { applyQueryConfirmationDialog.hide(); } showWaitingDialog(datasourceMessages.getString("DatasourceController.GENERATE_MODEL"), datasourceMessages .getString("DatasourceController.WAIT")); service.generateLogicalModel(datasourceModel.getRelationalModel().getDatasourceName(), datasourceModel.getRelationalModel() .getSelectedConnection().getName(), datasourceModel.getRelationalModel().getQuery(), datasourceModel .getRelationalModel().getPreviewLimit(), new XulServiceCallback<BusinessData>() { public void error(String message, Throwable error) { hideWaitingDialog(); query.setDisabled(false); displayErrorMessage(error); } public void success(BusinessData businessData) { try { hideWaitingDialog(); // merge any potential changes from earlier models if (datasourceModel.getRelationalModel().getBusinessData() != null) { Domain oldDomain = datasourceModel.getRelationalModel().getBusinessData().getDomain(); Domain newDomain = businessData.getDomain(); datasourceModel.copyOverMetadata(oldDomain, newDomain); } datasourceModel.getRelationalModel().setBusinessData(null); query.setDisabled(false); // Setting the editable property to true so that the table can be populated with correct cell types columnNameTreeCol.setEditable(true); columnTypeTreeCol.setEditable(true); //columnFormatTreeCol.setEditable(true); datasourceModel.getRelationalModel().setBusinessData(businessData); } catch (Exception xe) { xe.printStackTrace(); } } }); } public void editQuery() { } @Bindable public void closeConnectionDialog() { connectionDialog.hide(); } @Bindable public void closeApplyQueryConfirmationDialog() { applyQueryConfirmationDialog.hide(); } @Bindable public void displayPreview() { showWaitingDialog(datasourceMessages.getString("DatasourceController.GENERATE_PREVIEW_DATA"), datasourceMessages .getString("DatasourceController.WAIT")); service.doPreview(datasourceModel.getRelationalModel().getSelectedConnection().getName(), datasourceModel .getRelationalModel().getQuery(), datasourceModel.getRelationalModel().getPreviewLimit(), new XulServiceCallback<SerializedResultSet>() { public void error(String message, Throwable error) { hideWaitingDialog(); displayErrorMessage(error); } public void success(SerializedResultSet rs) { try { List<List<String>> data = rs.getData(); String[] columns = rs.getColumns(); int columnCount = columns.length; // Remove any existing children List<XulComponent> previewResultsList = previewResultsTable.getChildNodes(); for (int i = 0; i < previewResultsList.size(); i++) { previewResultsTable.removeChild(previewResultsList.get(i)); } XulTreeChildren treeChildren = previewResultsTable.getRootChildren(); if (treeChildren != null) { treeChildren.removeAll(); } // Remove all the existing columns int curTreeColCount = previewResultsTable.getColumns().getColumnCount(); List<XulComponent> cols = previewResultsTable.getColumns().getChildNodes(); for (int i = 0; i < curTreeColCount; i++) { previewResultsTable.getColumns().removeChild(cols.get(i)); } previewResultsTable.update(); // Recreate the colums XulTreeCols treeCols = previewResultsTable.getColumns(); if (treeCols == null) { try { treeCols = (XulTreeCols) document.createElement("treecols"); } catch (XulException e) { } } // Setting column data for (int i = 0; i < columnCount; i++) { try { XulTreeCol treeCol = (XulTreeCol) document.createElement("treecol"); treeCol.setLabel(columns[i]); treeCol.setWidth(columns[i].length() + 120); treeCols.addColumn(treeCol); } catch (XulException e) { } } XulTreeCols treeCols1 = previewResultsTable.getColumns(); int count = previewResultsTable.getColumns().getColumnCount(); // Create the tree children and setting the data try { for (int i = 0; i < data.size(); i++) { XulTreeRow row = (XulTreeRow) document.createElement("treerow"); for (int j = 0; j < columnCount; j++) { XulTreeCell cell = (XulTreeCell) document.createElement("treecell"); cell.setLabel(getCellData(data, i, j)); row.addCell(cell); } previewResultsTable.addTreeRow(row); } previewResultsTable.update(); hideWaitingDialog(); previewResultsDialog.show(); } catch (XulException e) { // TODO: add logging hideWaitingDialog(); System.out.println(e.getMessage()); e.printStackTrace(); } } catch (Exception e) { hideWaitingDialog(); displayErrorMessage(e); } } }); } @Bindable public void closePreviewResultsDialog() { previewResultsDialog.hide(); } public IXulAsyncDatasourceService getService() { return service; } public void setService(IXulAsyncDatasourceService service) { this.service = service; } @Bindable public void openErrorDialog(String title, String message) { errorDialog.setTitle(title); errorLabel.setValue(message); errorDialog.show(); } @Bindable public void closeErrorDialog() { if (!errorDialog.isHidden()) { errorDialog.hide(); } } @Bindable public void openSuccesDialog(String title, String message) { successDialog.setTitle(title); successLabel.setValue(message); successDialog.show(); } @Bindable public void closeSuccessDialog() { if (!successDialog.isHidden()) { successDialog.hide(); } } /* public void showWaitingDialog(String title, String message) { getWaitingDialog().setTitle(title); getWaitingDialog().setMessage(message); getWaitingDialog().show(); } public void hideWaitingDialog() { getWaitingDialog().hide(); } */ @Bindable public void showWaitingDialog(String title, String message) { waitingDialog.setTitle(title); waitingDialogLabel.setValue(message); waitingDialog.show(); } @Bindable public void hideWaitingDialog() { waitingDialog.hide(); } public void displayErrorMessage(Throwable th) { errorDialog.setTitle(ExceptionParser.getErrorHeader(th, getDatasourceMessages().getString("DatasourceEditor.USER_ERROR_TITLE"))); errorLabel.setValue(ExceptionParser.getErrorMessage(th, getDatasourceMessages().getString("DatasourceEditor.ERROR_0001_UNKNOWN_ERROR_HAS_OCCURED"))); errorDialog.show(); } /** * @param datasourceMessages the datasourceMessages to set */ public void setDatasourceMessages(DatasourceMessages datasourceMessages) { this.datasourceMessages = datasourceMessages; } /** * @return the datasourceMessages */ public DatasourceMessages getDatasourceMessages() { return datasourceMessages; } /** * @return the waitingDialog */ public WaitingDialog getWaitingDialog() { return this.waitingDialogBox; } /** * @param waitingDialog the waitingDialog to set */ public void setWaitingDialog(WaitingDialog waitingDialog) { this.waitingDialogBox = waitingDialog; } @Bindable public void closeAggregationEditorDialog() { aggregationCellEditor.hide(); } @Bindable public void saveAggregationValues() { aggregationCellEditor.notifyListeners(); } @Bindable public void closeSampleDataDialog() { sampleDataCellEditor.hide(); } private class CustomSampleDataCellEditor implements TreeCellEditor { XulDialog dialog = null; TreeCellEditorCallback callback = null; public CustomSampleDataCellEditor(XulDialog dialog) { super(); this.dialog = dialog; } public Object getValue() { // TODO Auto-generated method stub return null; } public void hide() { dialog.hide(); } public void setValue(Object val) { } public void show(int row, int col, Object boundObj, String columnBinding, TreeCellEditorCallback callback) { this.callback = callback; ModelDataRow modelDataRow = (ModelDataRow) boundObj; XulTreeCol column = sampleDataTree.getColumns().getColumn(0); column.setLabel(modelDataRow.getColumnName()); List<String> values = modelDataRow.getSampleDataList(); List<String> sampleDataList = new ArrayList<String>(); for (int i = 0; i < MAX_SAMPLE_DATA_ROWS && i < modelDataRow.getSampleDataList().size(); i++) { sampleDataList.add(values.get(i)); } sampleDataTree.setElements(sampleDataList); sampleDataTree.update(); dialog.setTitle(datasourceMessages.getString("DatasourceController.SAMPLE_DATA")); dialog.show(); } } private class CustomAggregationCellRenderer implements TreeCellRenderer { public Object getNativeComponent() { // TODO Auto-generated method stub return null; } public String getText(Object value) { StringBuffer buffer = new StringBuffer(); if (value instanceof Aggregation) { Aggregation aggregation = (Aggregation) value; List<AggregationType> aggregationList = aggregation.getAggregationList(); for (int i = 0; i < aggregationList.size(); i++) { if (buffer.length() + datasourceMessages.getString(aggregationList.get(i).getDescription()).length() < MAX_COL_SIZE) { buffer.append(datasourceMessages.getString(aggregationList.get(i).getDescription())); if ((i < aggregationList.size() - 1 && (buffer.length() + datasourceMessages.getString(aggregationList.get(i + 1).getDescription()).length() + COMMA.length() < MAX_COL_SIZE))) { buffer.append(COMMA); } } else { break; } } } return buffer.toString(); } public boolean supportsNativeComponent() { // TODO Auto-generated method stub return false; } } private class CustomSampleDataCellRenderer implements TreeCellRenderer { public Object getNativeComponent() { // TODO Auto-generated method stub return null; } public String getText(Object value) { if (value instanceof String) { return getSampleData((String) value); } else if (value instanceof Vector) { Vector<String> vectorValue = (Vector<String>) value; StringBuffer sampleDataBuffer = new StringBuffer(); for (int i = 0; i < vectorValue.size(); i++) { sampleDataBuffer.append(vectorValue.get(i)); } return getSampleData(sampleDataBuffer.toString()); } return EMPTY_STRING; } public boolean supportsNativeComponent() { // TODO Auto-generated method stub return false; } private String getSampleData(String sampleData) { if (sampleData != null && sampleData.length() > 0) { if (sampleData.length() <= MAX_COL_SIZE) { return sampleData; } else { return sampleData.substring(0, MAX_COL_SIZE); } } return EMPTY_STRING; } } public void initializeBusinessData(BusinessData businessData) { modelDataTable.update(); datasourceModel.setDatasourceType(DatasourceType.SQL); SqlPhysicalModel model = (SqlPhysicalModel) businessData.getDomain().getPhysicalModels().get(0); String queryStr = model.getPhysicalTables().get(0).getTargetTable(); // datasourceModel.setDatasourceType(DatasourceType.SQL); datasourceModel.getRelationalModel().setDatasourceName(businessData.getDomain().getId()); datasourceModel.getRelationalModel().setQuery(queryStr); for (IConnection conn : datasourceModel.getRelationalModel().getConnections()) { if (model.getDatasource().getDatabaseName().equals(conn.getName())) { datasourceModel.getRelationalModel().setSelectedConnection(conn); break; } } datasourceModel.getRelationalModel().setBusinessData(null); query.setDisabled(false); // Setting the editable property to true so that the table can be populated with correct cell types columnNameTreeCol.setEditable(true); columnTypeTreeCol.setEditable(true); //columnFormatTreeCol.setEditable(true); datasourceModel.getRelationalModel().setBusinessData(businessData); } public boolean supportsBusinessData(BusinessData businessData) { return (businessData.getDomain().getPhysicalModels().get(0) instanceof SqlPhysicalModel); } private String getCellData(List<List<String>> data, int rowNumber, int columnNumber) { String returnValue = null; int rowCount = 0; for (List<String> row : data) { if(rowCount == rowNumber) { returnValue = row.get(columnNumber); } rowCount++; } return returnValue; } }