/*!
* 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.controllers;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import org.pentaho.gwt.widgets.client.utils.NameUtils;
import org.pentaho.platform.dataaccess.datasource.IDatasourceInfo;
import org.pentaho.platform.dataaccess.datasource.ui.service.UIDatasourceServiceManager;
import org.pentaho.platform.dataaccess.datasource.utils.ExceptionParser;
import org.pentaho.platform.dataaccess.datasource.wizard.IDatasourceSummary;
import org.pentaho.platform.dataaccess.datasource.wizard.IWizardController;
import org.pentaho.platform.dataaccess.datasource.wizard.IWizardDatasource;
import org.pentaho.platform.dataaccess.datasource.wizard.IWizardListener;
import org.pentaho.platform.dataaccess.datasource.wizard.IWizardStep;
import org.pentaho.platform.dataaccess.datasource.wizard.models.IWizardModel;
import org.pentaho.platform.dataaccess.datasource.wizard.service.IXulAsyncDSWDatasourceService;
import org.pentaho.platform.dataaccess.datasource.wizard.sources.dummy.DummyDatasource;
import org.pentaho.platform.dataaccess.datasource.wizard.sources.dummy.SelectDatasourceStep;
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.XulMenuList;
import org.pentaho.ui.xul.components.XulTextbox;
import org.pentaho.ui.xul.containers.XulDeck;
import org.pentaho.ui.xul.containers.XulDialog;
import org.pentaho.ui.xul.impl.AbstractXulEventHandler;
import org.pentaho.ui.xul.stereotype.Bindable;
/**
* The wizard-controler manages the navigation between the wizard-panes. All panes are organized as a list, where each
* panel cannot be enabled if the previous panels are not valid or enabled.
* <p/>
* It is possible to jump back to previous steps and change values there. In some cases, this will just update the
* model, but in some cases this will invalidate the subsequent steps (for instance, if the query has been changed).
*
* @author William Seyler
*/
public class MainWizardController extends AbstractXulEventHandler implements IWizardController {
private IWizardModel wizardModel;
private IXulAsyncDSWDatasourceService datasourceService;
private XulTextbox datasourceName;
private IWizardDatasource activeDatasource;
// private String invalidCharacters;
public static final String DEFAULT_INVALID_CHARACTERS = "$<>?%^*()!~:;[]{}|/"; //$NON-NLS-1$
// Binding converters
protected class BackButtonBindingConverter extends BindingConvertor<Integer, Boolean> {
/* (non-Javadoc)
* @see org.pentaho.ui.xul.binding.BindingConvertor#sourceToTarget(java.lang.Object)
*/
@Override
public Boolean sourceToTarget( final Integer value ) {
return !( value > 0 );
}
/* (non-Javadoc)
* @see org.pentaho.ui.xul.binding.BindingConvertor#targetToSource(java.lang.Object)
*/
@Override
public Integer targetToSource( final Boolean value ) {
return null;
}
}
private static final String DISABLED_PROPERTY_NAME = "disabled"; //$NON-NLS-1$
private static final String VALID_PROPERTY_NAME = "valid"; //$NON-NLS-1$
private static final String NEXT_BTN_ELEMENT_ID = "main_wizard_window_extra2"; //$NON-NLS-1$
private static final String BACK_BTN_ELEMENT_ID = "main_wizard_window_extra1"; //$NON-NLS-1$
private static final String FINISH_BTN_ELEMENT_ID = "main_wizard_window_accept"; //$NON-NLS-1$
private static final String CONTENT_DECK_ELEMENT_ID = "content_deck"; //$NON-NLS-1$
private ArrayList<IWizardStep> steps;
private int activeStep = -1; // bogus active step
private BindingFactory bf;
private XulDialog warningDialog;
private Binding nextButtonBinding, finishedButtonBinding;
private NotDisabledBindingConvertor notDisabledBindingConvertor;
private List<IWizardListener> wizardListeners = new ArrayList<IWizardListener>();
private XulDialog wizardDialog;
private XulDialog summaryDialog;
private XulMenuList datatypeMenuList;
private XulButton finishButton;
private DummyDatasource dummyDatasource = new DummyDatasource();
private SelectDatasourceStep selectDatasourceStep;
public MainWizardController( final BindingFactory bf, IWizardModel wizardModel,
IXulAsyncDSWDatasourceService datasourceService ) {
this.wizardModel = wizardModel;
this.datasourceService = datasourceService;
this.steps = new ArrayList<IWizardStep>();
this.bf = bf;
this.notDisabledBindingConvertor = new NotDisabledBindingConvertor();
}
public IWizardStep getStep( final int step ) {
return steps.get( step );
}
public int getStepCount() {
return steps.size();
}
public void setActiveStep( final int step ) {
try {
if ( this.steps == null || steps.isEmpty() ) {
return;
}
final int oldActiveStep = this.activeStep;
if ( oldActiveStep >= 0 ) {
final IWizardStep deactivatingWizardStep = steps.get( oldActiveStep );
if ( step > oldActiveStep ) {
if ( !deactivatingWizardStep.stepDeactivatingForward() ) {
return;
}
} else {
if ( !deactivatingWizardStep.stepDeactivatingReverse() ) {
return;
}
}
}
this.activeStep = step;
final IWizardStep activatingWizardStep = steps.get( activeStep );
updateBindings();
// update the controller panel
final XulDeck deck = (XulDeck) document.getElementById( CONTENT_DECK_ELEMENT_ID );
int index = deck.getChildNodes().indexOf( activatingWizardStep.getUIComponent() );
deck.setSelectedIndex( index );
selectDataSourceMenuList( activatingWizardStep, index );
activatingWizardStep.refresh();
if ( activeStep > oldActiveStep ) {
activatingWizardStep.stepActivatingForward();
} else {
activatingWizardStep.stepActivatingReverse();
}
this.firePropertyChange( ACTIVE_STEP_PROPERTY_NAME, oldActiveStep, this.activeStep );
} catch ( Exception e ) {
e.printStackTrace();
}
}
private void selectDataSourceMenuList( IWizardStep activatingWizardStep, int index ) {
if ( datatypeMenuList.getElements().size() > 0
&& index != -1
&& activeDatasource != null ) {
if ( activeDatasource.getId().equals( "NONE" ) ) {
this.datatypeMenuList.setSelectedIndex( 0 );
} else {
if ( activeDatasource.getId().equals( "CSV" ) ) {
this.datatypeMenuList.setSelectedIndex( 1 );
} else {
if ( activeDatasource.getId().equals( "MULTI-TABLE-DS" ) ) {
this.datatypeMenuList.setSelectedIndex( 3 );
} else {
if ( activeDatasource.getId().equals( "SQL-DS" ) ) {
this.datatypeMenuList.setSelectedIndex( 2 );
}
}
}
}
}
}
public int getActiveStep() {
return activeStep;
}
public void init() {
// We need the SelectDatasourceStep at all times, add it now
wizardDialog = (XulDialog) document.getElementById( "main_wizard_window" );
summaryDialog = (XulDialog) document.getElementById( "summaryDialog" );
finishButton = (XulButton) document.getElementById( FINISH_BTN_ELEMENT_ID );
datasourceName = (XulTextbox) document.getElementById( "datasourceName" ); //$NON-NLS-1$
bf.createBinding( datasourceName, "value", wizardModel, "datasourceName" );
wizardModel.addPropertyChangeListener( new PropertyChangeListener() {
@Override
public void propertyChange( PropertyChangeEvent propertyChangeEvent ) {
if ( propertyChangeEvent.getPropertyName().equals( "datasourceName" ) ) {
steps.get( activeStep ).setValid( steps.get( activeStep ).isValid() );
}
}
} );
bf.setBindingType( Binding.Type.ONE_WAY );
datatypeMenuList = (XulMenuList) document.getElementById( "datatypeMenuList" );
Binding datasourceBinding = bf.createBinding( wizardModel, "datasources", datatypeMenuList, "elements" );
bf.setBindingType( Binding.Type.ONE_WAY );
bf.createBinding( datatypeMenuList, "selectedItem", wizardModel, "selectedDatasource" );
bf.setBindingType( Binding.Type.ONE_WAY );
bf.createBinding( wizardModel, "selectedDatasource", this, "selectedDatasource" );
bf.createBinding( this, ACTIVE_STEP_PROPERTY_NAME, BACK_BTN_ELEMENT_ID, DISABLED_PROPERTY_NAME,
new BackButtonBindingConverter() );
dummyDatasource = ( (DummyDatasource) wizardModel.getDatasources().iterator().next() );
activeDatasource = dummyDatasource;
selectDatasourceStep = dummyDatasource.getSelectDatasourceStep();
try {
for ( IWizardDatasource datasource : wizardModel.getDatasources() ) {
datasource.init( getXulDomContainer(), wizardModel );
}
steps.add( selectDatasourceStep );
selectDatasourceStep.activating();
setActiveStep( 0 );
datasourceBinding.fireSourceChanged();
setSelectedDatasource( dummyDatasource );
datasourceService.getDatasourceIllegalCharacters( new XulServiceCallback<String>() {
@Override
public void success( String retVal ) {
}
@Override
public void error( String message, Throwable error ) {
}
} );
} catch ( XulException e ) {
MessageHandler.getInstance().showErrorDialog( "Error", e.getMessage() );
e.printStackTrace();
} catch ( InvocationTargetException e ) {
MessageHandler.getInstance().showErrorDialog( "Error", e.getMessage() );
e.printStackTrace();
}
}
public void resetSelectedDatasource() {
datatypeMenuList.setSelectedIndex( 0 );
}
@Bindable
public void setSelectedDatasource( IWizardDatasource datasource ) {
IWizardDatasource prevSelection = activeDatasource;
activeDatasource = datasource;
if ( datasource == null || prevSelection == activeDatasource ) {
return;
}
try {
datasource.activating();
if ( prevSelection != null ) {
steps.removeAll( prevSelection.getSteps() );
prevSelection.deactivating();
}
for ( int i = 1; i < datasource.getSteps().size(); i++ ) {
steps.add( datasource.getSteps().get( i ) );
}
steps.addAll( datasource.getSteps() );
wizardModel.setSelectedDatasource( datasource );
activeStep = 0;
updateBindings();
} catch ( XulException e ) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
}
public void reset() {
setActiveStep( 0 );
}
protected void updateBindings() {
// Destroy any old bindings
if ( nextButtonBinding != null ) {
nextButtonBinding.destroyBindings();
}
if ( finishedButtonBinding != null ) {
finishedButtonBinding.destroyBindings();
}
// Create new binding to the current wizard panel
bf.setBindingType( Binding.Type.ONE_WAY );
nextButtonBinding =
bf.createBinding( getStep( getActiveStep() ), VALID_PROPERTY_NAME, NEXT_BTN_ELEMENT_ID, DISABLED_PROPERTY_NAME,
notDisabledBindingConvertor );
finishedButtonBinding =
bf.createBinding( activeDatasource, FINISHABLE_PROPERTY_NAME, FINISH_BTN_ELEMENT_ID, DISABLED_PROPERTY_NAME,
notDisabledBindingConvertor );
try {
nextButtonBinding.fireSourceChanged();
finishedButtonBinding.fireSourceChanged();
} catch ( Exception e ) {
//TODO add some exception handling here.
}
}
@Bindable
public void cancel() {
setCancelled();
}
private void setCancelled() {
for ( IWizardListener wizardListener : wizardListeners ) {
wizardListener.onCancel();
}
}
@Bindable
// TODO: migrate to CSV datasource
public void finish() {
if ( finishButton.isDisabled() ) {
return;
}
finishButton.setDisabled( true );
final String datasourceName = this.wizardModel.getDatasourceName();
// Validating whether the datasource name contains any illegal characters
if ( isDatasourceNameValid( datasourceName ) ) {
UIDatasourceServiceManager manager = UIDatasourceServiceManager.getInstance();
manager.getIds( new XulServiceCallback<List<IDatasourceInfo>>() {
@Override
public void success( List<IDatasourceInfo> datasourceInfos ) {
finishButton.setDisabled( false );
boolean isEditing = wizardModel.isEditing();
List<String> datasourceNames = new ArrayList<String>();
for ( IDatasourceInfo datasourceInfo : datasourceInfos ) {
if ( datasourceInfo.getType().equals( "Data Source Wizard" ) || datasourceInfo.getType()
.equals( "Analysis" ) ) {
datasourceNames.add( datasourceInfo.getName() );
}
}
if ( datasourceNames.contains( datasourceName ) && !isEditing ) {
showWarningDialog();
} else {
setFinished();
}
}
@Override
public void error( String s, Throwable throwable ) {
finishButton.setDisabled( false );
throwable.printStackTrace();
MessageHandler.getInstance().showErrorDialog( throwable.getMessage() );
}
} );
} else {
finishButton.setDisabled( false );
MessageHandler.getInstance().showErrorDialog( "Error", MessageHandler//$NON-NLS-1$
.getString( "DatasourceEditor.ERROR_0005_INVALID_DATASOURCE_NAME",
NameUtils.reservedCharListForDisplay( " " ) ), true ); //$NON-NLS-1$
}
}
@Bindable
public void overwriteDialogAccept() {
warningDialog.hide();
setFinished();
}
@Bindable
public void overwriteDialogCancel() {
warningDialog.hide();
}
private void setFinished() {
wizardDialog.hide();
MessageHandler.getInstance().showWaitingDialog();
activeDatasource.onFinish( new XulServiceCallback<IDatasourceSummary>() {
@Override
public void success( IDatasourceSummary iDatasourceSummary ) {
iDatasourceSummary.getDomain().getLogicalModels().get( 0 )
.setProperty( "DatasourceType", activeDatasource.getId() );
for ( IWizardListener wizardListener : wizardListeners ) {
wizardListener.onFinish( iDatasourceSummary );
}
}
@Override
public void error( String s, Throwable throwable ) {
throwable.printStackTrace();
//TODO: improve error messaging
MessageHandler.getInstance().closeWaitingDialog();
MessageHandler.getInstance().showErrorDialog( "Error", ExceptionParser //$NON-NLS-1$
.getErrorMessage( throwable,
MessageHandler.getString( "DatasourceEditor.ERROR_0001_UNKNOWN_ERROR_HAS_OCCURED" ) ),
true ); //$NON-NLS-1$
}
} );
}
public void showWarningDialog() {
warningDialog = (XulDialog) xulDomContainer.getDocumentRoot().getElementById( "overwriteDialog" );
warningDialog.show();
}
// Button click methods
@Bindable
public void next() {
for ( int i = getActiveStep(); i < steps.size() - 1; i++ ) {
IWizardStep nextStep = getStep( i + 1 );
if ( !nextStep.isDisabled() ) {
setActiveStep( i + 1 );
break;
}
}
}
@Bindable
public void back() {
for ( int i = getActiveStep() - 1; i > -1; i-- ) {
IWizardStep lastStep = getStep( i );
if ( !lastStep.isDisabled() ) {
setActiveStep( i );
break;
}
}
}
// Stuff for XUL
@Override
public String getName() {
return "wizardController"; //$NON-NLS-1$
}
public void setBindingFactory( final BindingFactory bf ) {
this.bf = bf;
}
/* (non-Javadoc)
* @see org.pentaho.reporting.engine.classic.wizard.ui.xul.components.WizardController#getBindingFactory()
*/
public BindingFactory getBindingFactory() {
return bf;
}
class NotDisabledBindingConvertor extends BindingConvertor<Boolean, Boolean> {
public Boolean sourceToTarget( Boolean value ) {
return Boolean.valueOf( !value.booleanValue() );
}
public Boolean targetToSource( Boolean value ) {
return Boolean.valueOf( !value.booleanValue() );
}
}
public void addWizardListener( IWizardListener listener ) {
wizardListeners.add( listener );
}
public void removeWizardListener( IWizardListener listener ) {
wizardListeners.remove( listener );
}
@Bindable
public void editFieldSettings() {
summaryDialog.hide();
wizardDialog.show();
finishButton.setDisabled( false );
}
private boolean isDatasourceNameValid( String datasourceName ) {
return NameUtils.isValidFileName( datasourceName );
}
/**
* Checks that the String does not contain certain characters.
*
* @param str the String to check, may be null
* @param invalidChars an String of invalid chars, may be null
* @return true if it contains none of the invalid chars, or is null
*/
private boolean containsNone( String str, String invalidChars ) {
if ( str == null || invalidChars == null ) {
return true;
}
char[] invalidCharsArray = null;
int strSize = str.length();
if ( invalidChars != null ) {
invalidCharsArray = invalidChars.toCharArray();
}
int validSize = invalidCharsArray.length;
for ( int i = 0; i < strSize; i++ ) {
char ch = str.charAt( i );
for ( int j = 0; j < validSize; j++ ) {
if ( invalidCharsArray[ j ] == ch ) {
return false;
}
}
}
return true;
}
}