/*
* 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) 2009 Pentaho Corporation.. All rights reserved.
*/
package org.pentaho.platform.dataaccess.datasource.wizard.controllers;
import org.pentaho.platform.dataaccess.datasource.utils.ExceptionParser;
import org.pentaho.platform.dataaccess.datasource.wizard.*;
import org.pentaho.platform.dataaccess.datasource.wizard.models.IWizardModel;
import org.pentaho.platform.dataaccess.datasource.wizard.service.IXulAsyncDatasourceService;
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;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
/**
* 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 IXulAsyncDatasourceService datasourceService;
private SelectDatasourceStep datasourceStep;
private XulTextbox datasourceName;
private List<IWizardDatasource> datasources = new ArrayList<IWizardDatasource>();
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 final static String DISABLED_PROPERTY_NAME = "disabled"; //$NON-NLS-1$
private final static String VALID_PROPERTY_NAME = "valid"; //$NON-NLS-1$
private final static String NEXT_BTN_ELEMENT_ID = "main_wizard_window_extra2"; //$NON-NLS-1$
private final static String BACK_BTN_ELEMENT_ID = "main_wizard_window_extra1"; //$NON-NLS-1$
private final static String FINISH_BTN_ELEMENT_ID = "main_wizard_window_accept"; //$NON-NLS-1$
private final static 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, IXulAsyncDatasourceService 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();
if (activeStep > oldActiveStep) {
activatingWizardStep.stepActivatingForward();
} else {
activatingWizardStep.stepActivatingReverse();
}
// update the controller panel
final XulDeck deck = (XulDeck) document.getElementById(CONTENT_DECK_ELEMENT_ID);
deck.setSelectedIndex(deck.getChildNodes().indexOf(activatingWizardStep.getUIComponent()));
this.firePropertyChange(ACTIVE_STEP_PROPERTY_NAME, oldActiveStep, this.activeStep);
} catch(Exception e){
e.printStackTrace();
}
}
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.BI_DIRECTIONAL);
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) {
invalidCharacters = retVal;
}
@Override
public void error(String message, Throwable error) {
invalidCharacters = DEFAULT_INVALID_CHARACTERS;
}
});
} catch (XulException e) {
MessageHandler.getInstance().showErrorDialog("Error", e.getMessage());
e.printStackTrace();
} catch (InvocationTargetException e) {
MessageHandler.getInstance().showErrorDialog("Error", e.getMessage());
e.printStackTrace();
}
}
@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)) {
datasourceService.listDatasourceNames(new XulServiceCallback<List<String>>() {
@Override
public void success(List<String> datasourceNames) {
finishButton.setDisabled(false);
boolean isEditing = wizardModel.isEditing();
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", invalidCharacters), 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();
}
private boolean isDatasourceNameValid(String datasourceName) {
return containsNone(datasourceName, invalidCharacters);
}
/**
* 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;
}
}