/*! ****************************************************************************** * * Pentaho Data Integration * * Copyright (C) 2002-2016 by Pentaho : http://www.pentaho.com * ******************************************************************************* * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ******************************************************************************/ package org.pentaho.di.ui.spoon.trans; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.CTabItem; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.FormAttachment; import org.eclipse.swt.layout.FormData; import org.eclipse.swt.layout.FormLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.TableItem; import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.ToolBar; import org.pentaho.di.core.Const; import org.pentaho.di.core.Props; import org.pentaho.di.core.exception.KettleException; import org.pentaho.di.core.exception.KettleStepException; import org.pentaho.di.core.extension.ExtensionPointHandler; import org.pentaho.di.core.logging.KettleLogStore; import org.pentaho.di.core.row.RowMetaInterface; import org.pentaho.di.core.row.ValueMetaInterface; import org.pentaho.di.i18n.BaseMessages; import org.pentaho.di.i18n.GlobalMessages; import org.pentaho.di.trans.Trans; import org.pentaho.di.trans.TransAdapter; import org.pentaho.di.trans.TransMeta; import org.pentaho.di.trans.step.RowAdapter; import org.pentaho.di.trans.step.StepInterface; import org.pentaho.di.trans.step.StepMeta; import org.pentaho.di.trans.step.StepMetaDataCombi; import org.pentaho.di.ui.core.PropsUI; import org.pentaho.di.ui.core.dialog.ErrorDialog; import org.pentaho.di.ui.core.gui.GUIResource; import org.pentaho.di.ui.core.widget.ColumnInfo; import org.pentaho.di.ui.core.widget.TableView; import org.pentaho.di.ui.spoon.Spoon; import org.pentaho.di.ui.spoon.XulSpoonSettingsManager; import org.pentaho.di.ui.spoon.delegates.SpoonDelegate; import org.pentaho.di.ui.xul.KettleXulLoader; import org.pentaho.ui.xul.XulDomContainer; import org.pentaho.ui.xul.XulLoader; import org.pentaho.ui.xul.containers.XulToolbar; import org.pentaho.ui.xul.impl.XulEventHandler; import org.pentaho.ui.xul.swt.tags.SwtRadio; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.ResourceBundle; public class TransPreviewDelegate extends SpoonDelegate implements XulEventHandler { private static Class<?> PKG = Spoon.class; // for i18n purposes, needed by Translator2!! private static final String XUL_FILE_TRANS_PREVIEW_TOOLBAR = "ui/trans-preview-toolbar.xul"; private TransGraph transGraph; private CTabItem transPreviewTab; private XulToolbar toolbar; private Composite transPreviewComposite; protected Map<StepMeta, RowMetaInterface> previewMetaMap; protected Map<StepMeta, List<Object[]>> previewDataMap; protected Map<StepMeta, StringBuffer> previewLogMap; private Composite previewComposite; private Text logText; private TableView tableView; public enum PreviewMode { FIRST, LAST, OFF, } private PreviewMode previewMode; private StepMeta selectedStep; protected StepMeta lastSelectedStep; private SwtRadio firstRadio; private SwtRadio lastRadio; private SwtRadio offRadio; /** * @param spoon * @param transGraph */ public TransPreviewDelegate( Spoon spoon, TransGraph transGraph ) { super( spoon ); this.transGraph = transGraph; previewMetaMap = new HashMap<StepMeta, RowMetaInterface>(); previewDataMap = new HashMap<StepMeta, List<Object[]>>(); previewLogMap = new HashMap<StepMeta, StringBuffer>(); previewMode = PreviewMode.FIRST; } public void showPreviewView() { if ( transPreviewTab == null || transPreviewTab.isDisposed() ) { addTransPreview(); } else { transPreviewTab.dispose(); transGraph.checkEmptyExtraView(); } } /** * Add a grid with the execution metrics per step in a table view * */ public void addTransPreview() { // First, see if we need to add the extra view... // if ( transGraph.extraViewComposite == null || transGraph.extraViewComposite.isDisposed() ) { transGraph.addExtraView(); } else { if ( transPreviewTab != null && !transPreviewTab.isDisposed() ) { // just set this one active and get out... // transGraph.extraViewTabFolder.setSelection( transPreviewTab ); return; } } transPreviewTab = new CTabItem( transGraph.extraViewTabFolder, SWT.NONE ); transPreviewTab.setImage( GUIResource.getInstance().getImagePreview() ); transPreviewTab.setText( BaseMessages.getString( PKG, "Spoon.TransGraph.PreviewTab.Name" ) ); transPreviewComposite = new Composite( transGraph.extraViewTabFolder, SWT.NONE ); transPreviewComposite.setLayout( new FormLayout() ); PropsUI.getInstance().setLook( transPreviewComposite, Props.WIDGET_STYLE_TOOLBAR ); addToolBar(); Control toolbarControl = (Control) toolbar.getManagedObject(); toolbarControl.setLayoutData( new FormData() ); FormData fd = new FormData(); fd.left = new FormAttachment( 0, 0 ); // First one in the left top corner fd.top = new FormAttachment( 0, 0 ); fd.right = new FormAttachment( 100, 0 ); toolbarControl.setLayoutData( fd ); toolbarControl.setParent( transPreviewComposite ); previewComposite = new Composite( transPreviewComposite, SWT.NONE ); previewComposite.setLayout( new FillLayout() ); FormData fdPreview = new FormData(); fdPreview.left = new FormAttachment( 0, 0 ); fdPreview.right = new FormAttachment( 100, 0 ); if ( Const.isLinux() ) { fdPreview.top = new FormAttachment( (Control) toolbar.getManagedObject(), 4 ); } else { fdPreview.top = new FormAttachment( (Control) toolbar.getManagedObject(), 10 ); } fdPreview.bottom = new FormAttachment( 100, 0 ); previewComposite.setLayoutData( fdPreview ); transPreviewTab.setControl( transPreviewComposite ); transGraph.extraViewTabFolder.setSelection( transPreviewTab ); transGraph.extraViewTabFolder.addSelectionListener( new SelectionAdapter() { @Override public void widgetSelected( SelectionEvent e ) { refreshView(); } } ); TransPreviewExtension extension = new TransPreviewExtension( transPreviewComposite, toolbarControl, previewComposite ); try { ExtensionPointHandler.callExtensionPoint( log, "TransPreviewCreated", extension ); } catch ( KettleException ex ) { log.logError( "Extension point call failed.", ex ); } } private void addToolBar() { try { XulLoader loader = new KettleXulLoader(); loader.setSettingsManager( XulSpoonSettingsManager.getInstance() ); ResourceBundle bundle = GlobalMessages.getBundle( "org/pentaho/di/ui/spoon/messages/messages" ); XulDomContainer xulDomContainer = loader.loadXul( XUL_FILE_TRANS_PREVIEW_TOOLBAR, bundle ); xulDomContainer.addEventHandler( this ); toolbar = (XulToolbar) xulDomContainer.getDocumentRoot().getElementById( "nav-toolbar" ); ToolBar swtToolBar = (ToolBar) toolbar.getManagedObject(); spoon.props.setLook( swtToolBar, Props.WIDGET_STYLE_TOOLBAR ); swtToolBar.layout( true, true ); swtToolBar.pack(); firstRadio = (SwtRadio) xulDomContainer.getDocumentRoot().getElementById( "preview-first" ); lastRadio = (SwtRadio) xulDomContainer.getDocumentRoot().getElementById( "preview-last" ); offRadio = (SwtRadio) xulDomContainer.getDocumentRoot().getElementById( "preview-off" ); PropsUI.getInstance().setLook( (Control) firstRadio.getManagedObject(), Props.WIDGET_STYLE_TOOLBAR ); PropsUI.getInstance().setLook( (Control) lastRadio.getManagedObject(), Props.WIDGET_STYLE_TOOLBAR ); PropsUI.getInstance().setLook( (Control) offRadio.getManagedObject(), Props.WIDGET_STYLE_TOOLBAR ); } catch ( Throwable t ) { log.logError( toString(), Const.getStackTracker( t ) ); new ErrorDialog( transPreviewComposite.getShell(), BaseMessages.getString( PKG, "Spoon.Exception.ErrorReadingXULFile.Title" ), BaseMessages.getString( PKG, "Spoon.Exception.ErrorReadingXULFile.Message", XUL_FILE_TRANS_PREVIEW_TOOLBAR ), new Exception( t ) ); } } /** * This refresh is driven by outside influenced using listeners and so on. */ public synchronized void refreshView() { if ( transGraph != null && transGraph.extraViewTabFolder != null ) { if ( transGraph.extraViewTabFolder.getSelection() != transPreviewTab ) { return; } } if ( previewComposite == null || previewComposite.isDisposed() ) { return; } // Which step do we preview... // StepMeta stepMeta = selectedStep; // copy to prevent race conditions and so on. if ( stepMeta == null ) { hidePreviewGrid(); return; } else { lastSelectedStep = selectedStep; } // Do we have a log for this selected step? // This means the preview work is still running or it error-ed out. // boolean errorStep = false; if ( transGraph.trans != null ) { List<StepInterface> steps = transGraph.trans.findBaseSteps( stepMeta.getName() ); if ( steps != null && steps.size() > 0 ) { errorStep = steps.get( 0 ).getErrors() > 0; } } StringBuffer logText = previewLogMap.get( stepMeta ); if ( errorStep && logText != null && logText.length() > 0 ) { showLogText( stepMeta, logText.toString() ); return; } // If the preview work is done we have row meta-data and data for each step. // RowMetaInterface rowMeta = previewMetaMap.get( stepMeta ); if ( rowMeta != null ) { List<Object[]> rowData = previewDataMap.get( stepMeta ); try { showPreviewGrid( transGraph.getManagedObject(), stepMeta, rowMeta, rowData ); } catch ( Exception e ) { e.printStackTrace(); logText.append( Const.getStackTracker( e ) ); showLogText( stepMeta, logText.toString() ); } } } protected void hidePreviewGrid() { if ( tableView != null && !tableView.isDisposed() ) { tableView.dispose(); } } protected void showPreviewGrid( TransMeta transMeta, StepMeta stepMeta, RowMetaInterface rowMeta, List<Object[]> rowsData ) throws KettleException { clearPreviewComposite(); ColumnInfo[] columnInfo = new ColumnInfo[rowMeta.size()]; for ( int i = 0; i < columnInfo.length; i++ ) { ValueMetaInterface valueMeta = rowMeta.getValueMeta( i ); columnInfo[i] = new ColumnInfo( valueMeta.getName(), ColumnInfo.COLUMN_TYPE_TEXT, false, true ); columnInfo[i].setValueMeta( valueMeta ); } tableView = new TableView( transMeta, previewComposite, SWT.NONE, columnInfo, rowsData.size(), null, PropsUI .getInstance() ); // Put data on it... // for ( int rowNr = 0; rowNr < rowsData.size(); rowNr++ ) { Object[] rowData = rowsData.get( rowNr ); TableItem item; if ( rowNr < tableView.table.getItemCount() ) { item = tableView.table.getItem( rowNr ); } else { item = new TableItem( tableView.table, SWT.NONE ); } for ( int colNr = 0; colNr < rowMeta.size(); colNr++ ) { String string; ValueMetaInterface valueMetaInterface; try { valueMetaInterface = rowMeta.getValueMeta( colNr ); if ( valueMetaInterface.isStorageBinaryString() ) { Object nativeType = valueMetaInterface.convertBinaryStringToNativeType( (byte[]) rowData[colNr] ); string = valueMetaInterface.getStorageMetadata().getString( nativeType ); } else { string = rowMeta.getString( rowData, colNr ); } } catch ( Exception e ) { string = "Conversion error: " + e.getMessage(); } if ( string == null ) { item.setText( colNr + 1, "<null>" ); item.setForeground( colNr + 1, GUIResource.getInstance().getColorBlue() ); } else { item.setText( colNr + 1, string ); } } } tableView.setRowNums(); tableView.setShowingConversionErrorsInline( true ); tableView.optWidth( true ); previewComposite.layout( true, true ); } protected void showLogText( StepMeta stepMeta, String loggingText ) { clearPreviewComposite(); logText = new Text( previewComposite, SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL ); logText.setText( loggingText ); previewComposite.layout( true, true ); } private void clearPreviewComposite() { // First clear out the preview composite, then put in a text field showing the log text // // for ( Control control : previewComposite.getChildren() ) { control.dispose(); } } public CTabItem getTransGridTab() { return transPreviewTab; } /* * (non-Javadoc) * * @see org.pentaho.ui.xul.impl.XulEventHandler#getData() */ public Object getData() { // TODO Auto-generated method stub return null; } /* * (non-Javadoc) * * @see org.pentaho.ui.xul.impl.XulEventHandler#getName() */ public String getName() { return "transpreview"; } /* * (non-Javadoc) * * @see org.pentaho.ui.xul.impl.XulEventHandler#getXulDomContainer() */ public XulDomContainer getXulDomContainer() { // TODO Auto-generated method stub return null; } /* * (non-Javadoc) * * @see org.pentaho.ui.xul.impl.XulEventHandler#setData(java.lang.Object) */ public void setData( Object data ) { // TODO Auto-generated method stub } /* * (non-Javadoc) * * @see org.pentaho.ui.xul.impl.XulEventHandler#setName(java.lang.String) */ public void setName( String name ) { // TODO Auto-generated method stub } /* * (non-Javadoc) * * @see org.pentaho.ui.xul.impl.XulEventHandler#setXulDomContainer(org.pentaho. ui.xul.XulDomContainer) */ public void setXulDomContainer( XulDomContainer xulDomContainer ) { // TODO Auto-generated method stub } /** * @return the active */ public boolean isActive() { return previewMode != PreviewMode.OFF; } public void setPreviewMode( PreviewMode previewMode ) { this.previewMode = previewMode; } public void capturePreviewData( final Trans trans, List<StepMeta> stepMetas ) { final StringBuffer loggingText = new StringBuffer(); // First clean out previous preview data. Otherwise this method leaks memory like crazy. // previewLogMap.clear(); previewMetaMap.clear(); previewDataMap.clear(); try { final TransMeta transMeta = trans.getTransMeta(); for ( final StepMeta stepMeta : stepMetas ) { final RowMetaInterface rowMeta = transMeta.getStepFields( stepMeta ).clone(); previewMetaMap.put( stepMeta, rowMeta ); final List<Object[]> rowsData; if ( previewMode == PreviewMode.LAST ) { rowsData = new LinkedList<Object[]>(); } else { rowsData = new ArrayList<Object[]>(); } previewDataMap.put( stepMeta, rowsData ); previewLogMap.put( stepMeta, loggingText ); StepInterface step = trans.findRunThread( stepMeta.getName() ); if ( step != null ) { switch ( previewMode ) { case LAST: step.addRowListener( new RowAdapter() { @Override public void rowWrittenEvent( RowMetaInterface rowMeta, Object[] row ) throws KettleStepException { try { rowsData.add( rowMeta.cloneRow( row ) ); if ( rowsData.size() > PropsUI.getInstance().getDefaultPreviewSize() ) { rowsData.remove( 0 ); } } catch ( Exception e ) { throw new KettleStepException( "Unable to clone row for metadata : " + rowMeta, e ); } } } ); break; default: step.addRowListener( new RowAdapter() { @Override public void rowWrittenEvent( RowMetaInterface rowMeta, Object[] row ) throws KettleStepException { if ( rowsData.size() < PropsUI.getInstance().getDefaultPreviewSize() ) { try { rowsData.add( rowMeta.cloneRow( row ) ); } catch ( Exception e ) { throw new KettleStepException( "Unable to clone row for metadata : " + rowMeta, e ); } } } } ); break; } } } } catch ( Exception e ) { loggingText.append( Const.getStackTracker( e ) ); } // In case there were errors during preview... // trans.addTransListener( new TransAdapter() { @Override public void transFinished( Trans trans ) throws KettleException { // Copy over the data from the previewDelegate... // if ( trans.getErrors() != 0 ) { // capture logging and store it... // for ( StepMetaDataCombi combi : trans.getSteps() ) { if ( combi.copy == 0 ) { StringBuffer logBuffer = KettleLogStore.getAppender().getBuffer( combi.step.getLogChannel().getLogChannelId(), false ); previewLogMap.put( combi.stepMeta, logBuffer ); } } } } } ); } public void addPreviewData( StepMeta stepMeta, RowMetaInterface rowMeta, List<Object[]> rowsData, StringBuffer buffer ) { previewLogMap.put( stepMeta, buffer ); previewMetaMap.put( stepMeta, rowMeta ); previewDataMap.put( stepMeta, rowsData ); } /** * @return the selectedStep */ public StepMeta getSelectedStep() { return selectedStep; } /** * @param selectedStep * the selectedStep to set */ public void setSelectedStep( StepMeta selectedStep ) { this.selectedStep = selectedStep; } public PreviewMode getPreviewMode() { return previewMode; } public void first() { previewMode = PreviewMode.FIRST; firstRadio.setSelected( true ); lastRadio.setSelected( false ); offRadio.setSelected( false ); } public void last() { previewMode = PreviewMode.LAST; firstRadio.setSelected( false ); lastRadio.setSelected( true ); offRadio.setSelected( false ); } public void off() { previewMode = PreviewMode.OFF; firstRadio.setSelected( false ); lastRadio.setSelected( false ); offRadio.setSelected( true ); } public Map<StepMeta, List<Object[]>> getPreviewDataMap() { return previewDataMap; } }