/*!
* 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 (c) 2002-2013 Pentaho Corporation.. All rights reserved.
*/
package org.pentaho.platform.dataaccess.datasource.wizard.sources.csv;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.pentaho.platform.dataaccess.datasource.Delimiter;
import org.pentaho.platform.dataaccess.datasource.Enclosure;
import org.pentaho.platform.dataaccess.datasource.wizard.AbstractWizardStep;
import org.pentaho.platform.dataaccess.datasource.wizard.controllers.MessageHandler;
import org.pentaho.platform.dataaccess.datasource.wizard.models.CsvFileInfo;
import org.pentaho.platform.dataaccess.datasource.wizard.models.DatasourceModel;
import org.pentaho.platform.dataaccess.datasource.wizard.models.IModelInfoValidationListener;
import org.pentaho.platform.dataaccess.datasource.wizard.service.gwt.ICsvDatasourceServiceAsync;
import org.pentaho.ui.xul.XulComponent;
import org.pentaho.ui.xul.binding.Binding;
import org.pentaho.ui.xul.binding.BindingConvertor;
import org.pentaho.ui.xul.components.XulImage;
import org.pentaho.ui.xul.components.XulLabel;
import org.pentaho.ui.xul.components.XulMenuList;
import org.pentaho.ui.xul.components.XulTextbox;
import org.pentaho.ui.xul.containers.XulRow;
import org.pentaho.ui.xul.stereotype.Bindable;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;
/**
* User: nbaker Date: 3/22/11
*/
public class CsvPhysicalStep extends AbstractWizardStep {
public static final int DEFAULT_CSV_TABLE_ROW_COUNT = 7;
private XulMenuList<String> encodingTypeMenuList = null;
private static final List<String> ENCODINGS = Arrays
.asList( "", "UTF-8", "UTF-16BE", "UTF-16LE", "UTF-32BE", "UTF-32LE", "Shift_JIS", "ISO-2022-JP", "ISO-2022-CN",
"ISO-2022-KR", "GB18030", "Big5", "EUC-JP", "EUC-KR", "ISO-8859-1", "ISO-8859-2", "ISO-8859-5", "ISO-8859-6",
"ISO-8859-7", "ISO-8859-8", "windows-1251", "windows-1256", "KOI8-R", "ISO-8859-9" );
private XulTextbox uploadedFileTextBox;
private XulLabel csvTextPreview;
private DatasourceModel datasourceModel;
private ICsvDatasourceServiceAsync csvDatasourceService;
public CsvPhysicalStep( DatasourceModel datasourceModel, CsvDatasource parentDatasource,
ICsvDatasourceServiceAsync csvDatasourceService ) {
super( parentDatasource );
this.datasourceModel = datasourceModel;
this.csvDatasourceService = csvDatasourceService;
}
@Override
public void activating() {
parentDatasource.setFinishable( false );
// This step takes the place of the first. We'll grab references to it's elements added to the left
stepRow = (XulRow) document.getElementById( STEP_ROWS_ID ).getFirstChild();
stepImage = (XulImage) stepRow.getFirstChild();
stepLabel = (XulLabel) stepRow.getChildNodes().get( 1 );
}
@Override
public void deactivate() {
}
@Override
public XulComponent getUIComponent() {
return document.getElementById( "csvDeckPanel" );
}
@Override
public String getStepName() {
return "CSV Physical";
}
@Override
public void setBindings() {
uploadedFileTextBox = (XulTextbox) document.getElementById( "uploadedFile" ); //$NON-NLS-1$
csvTextPreview = (XulLabel) document.getElementById( "csvTextPreview" ); //$NON-NLS-1$
encodingTypeMenuList = (XulMenuList<String>) document.getElementById( "encodingTypeMenuList" );
encodingTypeMenuList.setElements( ENCODINGS );
BindingConvertor<Integer, Boolean> isFirstRowHeaderConverter = BindingConvertor.integer2Boolean();
bf.createBinding(
datasourceModel.getModelInfo().getFileInfo(),
CsvFileInfo.HEADER_ROWS_ATTRIBUTE,
"isHeaderCheckBox", //$NON-NLS-1$
"checked", //$NON-NLS-1$
isFirstRowHeaderConverter );
// Binding convertor to between Delimiter and radio group selected value
BindingConvertor<String, String> delimiterBindingConvertor = new BindingConvertor<String, String>() {
public String sourceToTarget( String source ) {
Delimiter delimiter = Delimiter.lookupValue( source );
if ( delimiter != null ) {
return Delimiter.lookupValue( source ).getName();
} else {
return source;
}
}
public String targetToSource( String target ) {
Delimiter delimiter = Delimiter.lookupName( target );
if ( delimiter != null ) {
return delimiter.getValue();
} else {
return target;
}
}
};
// add binding for the Delimiter to it's corresponding radio group
bf.createBinding(
datasourceModel.getModelInfo().getFileInfo(),
CsvFileInfo.DELIMITER_ATTRIBUTE,
"delimiterRadioGroup", //$NON-NLS-1$
"value", //$NON-NLS-1$
delimiterBindingConvertor );
// Binding convertor to between Enclosure and radio group selected value
BindingConvertor<String, String> enclosureBindingConvertor = new BindingConvertor<String, String>() {
public String sourceToTarget( String source ) {
Enclosure e = Enclosure.lookupValue( source );
if ( e == null ) {
e = Enclosure.NONE;
}
return e.getName();
}
public String targetToSource( String target ) {
Enclosure e = Enclosure.lookupName( target );
if ( e == Enclosure.NONE ) {
return null;
} else {
return e.getValue();
}
}
};
// add binding for the Enclosure to it's corresponding radio group
bf.createBinding( datasourceModel.getModelInfo().getFileInfo(),
CsvFileInfo.ENCLOSURE_ATTRIBUTE,
"enclosureRadioGroup", //$NON-NLS-1$
"value", //$NON-NLS-1$
enclosureBindingConvertor );
// when the delimiter changes, we need to refresh the preview
datasourceModel.getModelInfo().getFileInfo()
.addPropertyChangeListener( CsvFileInfo.DELIMITER_ATTRIBUTE, new RefreshPreviewPropertyChangeListener() );
// when the enclosure changes, we need to refresh the preview
datasourceModel.getModelInfo().getFileInfo()
.addPropertyChangeListener( CsvFileInfo.ENCLOSURE_ATTRIBUTE, new RefreshPreviewPropertyChangeListener() );
// when the first-row-is-header flag changes, we need to refresh the preview
datasourceModel.getModelInfo().getFileInfo()
.addPropertyChangeListener( CsvFileInfo.HEADER_ROWS_ATTRIBUTE, new RefreshPreviewPropertyChangeListener() );
uploadedFileTextBox.addPropertyChangeListener( new PropertyChangeListener() {
public void propertyChange( PropertyChangeEvent evt ) {
if ( evt.getPropertyName().equals( "value" ) ) {
// set the defaults if none already selected
String delimiter = datasourceModel.getModelInfo().getFileInfo().getDelimiter();
if ( delimiter == null || delimiter.equals( "" ) ) {
datasourceModel.getModelInfo().getFileInfo().setDelimiter( "," );
datasourceModel.getModelInfo().getFileInfo().setHeaderRows( 1 );
}
String enclosure = datasourceModel.getModelInfo().getFileInfo().getEnclosure();
if ( enclosure == null || enclosure.equals( "" ) ) {
datasourceModel.getModelInfo().getFileInfo().setEnclosure( "\"" );
}
syncModelInfo();
datasourceModel.getGuiStateModel().setDirty( true );
datasourceModel.getModelInfo().validate();
}
}
} );
bf.setBindingType( Binding.Type.ONE_WAY );
// binding to set the first-row-is-header checkbox's enabled property based on the selectedItem in the filesList
bf.createBinding( uploadedFileTextBox, "value", "isHeaderCheckBox", "!disabled",
BindingConvertor.object2Boolean() );
// binding to set the delimiters enabled property based on the selectedItem in the filesList
bf.createBinding( uploadedFileTextBox, "value", "delimiterRadioGroup", "!disabled",
BindingConvertor.object2Boolean() );
// binding to set the enclosures enabled property based on the selectedItem in the filesList
bf.createBinding( uploadedFileTextBox, "value", "enclosureRadioGroup", "!disabled",
BindingConvertor.object2Boolean() );
bf.createBinding( datasourceModel.getModelInfo().getFileInfo(), "friendlyFilename", uploadedFileTextBox,
"value" ); //$NON-NLS-1$ //$NON-NLS-2$
bf.createBinding( uploadedFileTextBox, "value", "encodingTypeMenuList", "!disabled",
BindingConvertor.object2Boolean() );
BindingConvertor<String, String> encodingBindingConvertor = new BindingConvertor<String, String>() {
public String sourceToTarget( String source ) {
return source;
}
public String targetToSource( String target ) {
Collection<String> encodings = encodingTypeMenuList.getElements();
if ( target != null && !encodings.contains( target ) ) {
encodings.add( target );
encodingTypeMenuList.setElements( encodings );
}
return target;
}
};
bf.setBindingType( Binding.Type.BI_DIRECTIONAL );
bf.createBinding( encodingTypeMenuList, "value", datasourceModel.getModelInfo().getFileInfo(), CsvFileInfo.ENCODING,
encodingBindingConvertor ); //$NON-NLS-1$ //$NON-NLS-2$
RefreshPreviewPropertyChangeListener previewChangeListener = new RefreshPreviewPropertyChangeListener();
datasourceModel.getModelInfo().getFileInfo()
.addPropertyChangeListener( CsvFileInfo.ENCODING, previewChangeListener );
datasourceModel.getModelInfo().getFileInfo()
.addPropertyChangeListener( CsvFileInfo.TMP_FILENAME_ATTRIBUTE, previewChangeListener );
}
@Override
public void stepActivatingReverse() {
super.stepActivatingReverse();
parentDatasource.setFinishable( false );
}
/**
* Executes when refresh of preview is required (also effective when dirty flag should be set)
*/
private class RefreshPreviewPropertyChangeListener implements PropertyChangeListener {
public void propertyChange( PropertyChangeEvent evt ) {
datasourceModel.getGuiStateModel().setDirty( true );
try {
String propName = evt.getPropertyName();
if ( propName.equals( CsvFileInfo.ENCODING ) || propName.equals( CsvFileInfo.TMP_FILENAME_ATTRIBUTE ) ) {
csvDatasourceService.getPreviewRows( datasourceModel.getModelInfo().getFileInfo().getTmpFilename(),
datasourceModel.getModelInfo().getFileInfo().getHeaderRows() > 0,
10, datasourceModel.getModelInfo().getFileInfo().getEncoding(),
new AsyncCallback<List<String>>() {
public void onSuccess( List<String> lines ) {
try {
datasourceModel.getModelInfo().getFileInfo().setContents( lines );
refreshPreview();
} catch ( Exception e ) {
GWT.log( "Had an issue refreshing the data preview", e ); //$NON-NLS-1$
}
}
public void onFailure( Throwable th ) {
GWT.log( th.toString() );
}
} );
}
refreshPreview();
} catch ( Exception e ) {
GWT.log( e.toString() );
}
datasourceModel.getModelInfo().validate();
datasourceModel.getModelInfo().addModelInfoValidationListener( new IModelInfoValidationListener() {
@Override
public void onCsvInValid() {
setValid( isValidated() );
}
@Override
public void onCsvValid() {
setValid( isValidated() );
}
@Override
public void onModelInfoValid() {
setValid( isValidated() );
}
@Override
public void onModelInfoInvalid() {
setValid( isValidated() );
}
} );
}
}
private boolean isValidated() {
return datasourceModel.getModelInfo().getStageTableName() != null
&& datasourceModel.getModelInfo().getStageTableName().trim().length() > 0
&& datasourceModel.getModelInfo().getFileInfo() != null
&& datasourceModel.getModelInfo().getFileInfo().getTmpFilename() != null
&& datasourceModel.getModelInfo().getFileInfo().getTmpFilename().length() > 0
&& datasourceModel.getModelInfo().getFileInfo().getDelimiter() != null
&& datasourceModel.getModelInfo().getFileInfo().getDelimiter().length() > 0;
}
public void syncModelInfo() {
String filename = datasourceModel.getModelInfo().getFileInfo().getFilename();
String tmpFilename = datasourceModel.getModelInfo().getFileInfo().getTmpFilename();
if ( ( filename == null ) || ( tmpFilename != null && !tmpFilename
.startsWith( filename ) ) ) { //creating a brand new ds || editing a ds having uploaded a new file
filename = tmpFilename;
} else if ( tmpFilename == null || tmpFilename
.startsWith( filename ) ) { // editing a ds without uploading a new file
datasourceModel.getModelInfo().getFileInfo().setTmpFilename( filename );
}
if ( filename != null ) {
csvDatasourceService.getEncoding( filename, new AsyncCallback<String>() {
public void onSuccess( String encoding ) {
datasourceModel.getModelInfo().getFileInfo().setEncodingFromServer( encoding );
try {
// go get the file contents of the selected file
csvDatasourceService.getPreviewRows( datasourceModel.getModelInfo().getFileInfo().getTmpFilename(),
datasourceModel.getModelInfo().getFileInfo().getHeaderRows() > 0,
10, encoding,
new AsyncCallback<List<String>>() {
public void onSuccess( List<String> lines ) {
try {
datasourceModel.getModelInfo().getFileInfo().setContents( lines );
refreshPreview();
} catch ( Exception e ) {
GWT.log( "Had an issue refreshing the data preview", e ); //$NON-NLS-1$
}
}
public void onFailure( Throwable th ) {
MessageHandler.getInstance().showErrorDialog( th.getMessage() );
}
} );
} catch ( Exception e ) {
MessageHandler.getInstance().showErrorDialog( e.getMessage() );
}
}
public void onFailure( Throwable th ) {
GWT.log( "Had an issue getting the encoding type", th ); //$NON-NLS-1$
}
} );
} else {
try {
refreshPreview();
} catch ( Exception e ) {
GWT.log( "Had an issue refreshing the data preview", e ); //$NON-NLS-1$
}
}
}
@Bindable
public void refreshPreview() throws Exception {
csvTextPreview.setValue( "" ); //$NON-NLS-1$
CsvFileInfo fileInfo = datasourceModel.getModelInfo().getFileInfo();
csvTextPreview.setValue( fileInfo.formatSampleContents() );
}
@Override
public void refresh() {
}
}