/*
* 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.reporting.engine.classic.wizard.ui.xul.steps;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import org.pentaho.reporting.engine.classic.core.AbstractReportDefinition;
import org.pentaho.reporting.engine.classic.core.CompoundDataFactory;
import org.pentaho.reporting.engine.classic.core.DataFactory;
import org.pentaho.reporting.engine.classic.core.ReportDataFactoryException;
import org.pentaho.reporting.engine.classic.core.designtime.DataSourcePlugin;
import org.pentaho.reporting.engine.classic.core.designtime.DesignTimeContext;
import org.pentaho.reporting.engine.classic.core.metadata.DataFactoryMetaData;
import org.pentaho.reporting.engine.classic.core.metadata.DataFactoryRegistry;
import org.pentaho.reporting.engine.classic.core.StaticDataRow;
import org.pentaho.reporting.engine.classic.core.wizard.DataSchemaModel;
import org.pentaho.reporting.engine.classic.wizard.ui.xul.WizardEditorModel;
import org.pentaho.reporting.engine.classic.wizard.ui.xul.components.AbstractWizardStep;
import org.pentaho.reporting.libraries.base.util.DebugLog;
import org.pentaho.reporting.libraries.base.util.ObjectUtilities;
import org.pentaho.reporting.libraries.base.util.StringUtils;
import org.pentaho.ui.xul.XulDomContainer;
import org.pentaho.ui.xul.XulException;
import org.pentaho.ui.xul.binding.Binding.Type;
import org.pentaho.ui.xul.binding.BindingConvertor;
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.impl.AbstractXulEventHandler;
import org.pentaho.ui.xul.util.AbstractModelNode;
/**
* TODO: Document Me
*
* @author William Seyler
*/
public class DataSourceAndQueryStep extends AbstractWizardStep
{
private DesignTimeContext designTimeContext;
private enum DATASOURCE_TYPE
{
ROOT, DATAFACTORY, CONNECTION, QUERY
}
private static class DataFactoryMetaDataComparator implements Comparator<DataFactoryMetaData>
{
private DataFactoryMetaDataComparator()
{
}
public int compare(final DataFactoryMetaData o1, final DataFactoryMetaData o2)
{
return o1.getDisplayName(Locale.getDefault()).compareTo(o2.getDisplayName(Locale.getDefault()));
}
}
private class SelectedIndexUpdateHandler implements PropertyChangeListener
{
private SelectedIndexUpdateHandler()
{
}
public void propertyChange(final PropertyChangeEvent evt)
{
if (SELECTED_INDEX_PROPERTY_NAME.equals(evt.getPropertyName()))
{
final XulDialog datasourceType = (XulDialog) getDocument().getElementById(DATASOURCE_TYPE_DIALOG_ID);
datasourceType.setVisible(false);
}
}
}
protected static class XulEditorDataFactoryMetaData
{
private DataFactoryMetaData metadata;
public XulEditorDataFactoryMetaData(final DataFactoryMetaData metadata)
{
if (metadata == null)
{
throw new NullPointerException();
}
this.metadata = metadata;
}
public String getName()
{
return metadata.getDisplayName(Locale.getDefault());
}
public DataFactoryMetaData getMetadata()
{
return metadata;
}
public String toString()
{
return getName();
}
}
/**
* @author wseyler
*/
private class CurrentQueryBindingConverter extends BindingConvertor<DatasourceModelNode, String>
{
/* (non-Javadoc)
* @see org.pentaho.ui.xul.binding.BindingConvertor#sourceToTarget(java.lang.Object)
*/
@Override
public String sourceToTarget(final DatasourceModelNode value)
{
if (value != null && value.getType() == DATASOURCE_TYPE.QUERY)
{
return value.getValue();
}
return DataSourceAndQueryStep.this.getCurrentQuery();
}
/* (non-Javadoc)
* @see org.pentaho.ui.xul.binding.BindingConvertor#targetToSource(java.lang.Object)
*/
@Override
public DatasourceModelNode targetToSource(final String value)
{
// not used for one way binding
return null;
}
}
protected class DatasourceAndQueryStepHandler extends AbstractXulEventHandler
{
public DatasourceAndQueryStepHandler()
{
}
public String getName()
{
return HANDLER_NAME;
}
public void doCreateDataFactory()
{
DataSourceAndQueryStep.this.createDataFactory();
}
public void doEditDatasource()
{
final XulTree tree = (XulTree) document.getElementById(DATASOURCES_TREE_ID);
final DatasourceModelNode node = (DatasourceModelNode) tree.getSelectedItem();
switch (node.getType())
{
case CONNECTION:
final DataFactory df = (DataFactory) node.getUserObject();
final DataFactoryMetaData o = getMetaForDataFactory(df, dataFactoryMetas);
editOrCreateDataFactory(o);
break;
case QUERY:
editQuery(node.getValue());
break;
default:
break;
}
}
public void doDeleteDatasourceItem()
{
final XulTree tree = (XulTree) document.getElementById(DATASOURCES_TREE_ID);
final DatasourceModelNode node = (DatasourceModelNode) tree.getSelectedItem();
switch (node.getType())
{
case DATAFACTORY:
deleteDataFactory((DataFactoryMetaData) node.getUserObject());
break;
case CONNECTION:
deleteConnection((DataFactory) node.getUserObject());
default:
break;
}
updateDatasourceTree();
}
}
protected class DatasourceModelNode extends AbstractModelNode<DatasourceModelNode>
{
private DATASOURCE_TYPE type;
private String value;
private Object userObject;
public DatasourceModelNode(final String value, final Object userObject, final DATASOURCE_TYPE type)
{
this.value = value;
this.userObject = userObject;
this.type = type;
}
public String getValue()
{
return value;
}
public void setValue(final String value)
{
final String oldValue = this.value;
this.value = value;
this.firePropertyChange(VALUE_PROPERTY_NAME, oldValue, value);
}
public DATASOURCE_TYPE getType()
{
return type;
}
public void setType(final DATASOURCE_TYPE type)
{
this.type = type;
}
public Object getUserObject()
{
return userObject;
}
public void setUserObject(final Object userObject)
{
this.userObject = userObject;
}
}
private class IndiciesToBooleanBindingConverter extends BindingConvertor<int[], Boolean>
{
/* (non-Javadoc)
* @see org.pentaho.ui.xul.binding.BindingConvertor#sourceToTarget(java.lang.Object)
*/
@Override
public Boolean sourceToTarget(final int[] value)
{
return value.length > 0;
}
/* (non-Javadoc)
* @see org.pentaho.ui.xul.binding.BindingConvertor#targetToSource(java.lang.Object)
*/
@Override
public int[] targetToSource(final Boolean value)
{
// Not needed for one way binding
return null;
}
}
private static final String DATASOURCES_ROOT_NODE_NAME = "Datasources Root"; //$NON-NLS-1$
private static final String DATASOURCE_AND_QUERY_STEP_OVERLAY = "org/pentaho/reporting/engine/classic/wizard/ui/xul/res/datasource_and_query_step_Overlay.xul"; //$NON-NLS-1$
private static final String HANDLER_NAME = "datasource_and_query_step_handler"; //$NON-NLS-1$
private static final String DATASOURCES_TREE_ID = "datasources_tree"; //$NON-NLS-1$
private static final String DATASOURCE_TYPE_DIALOG_ID = "datasource_type_dialog"; //$NON-NLS-1$
private static final String DATASOURCE_SELECTIONS_BOX_ID = "datasource_selections_box"; //$NON-NLS-1$
private static final String EDIT_DATASOURCES_BTN_ID = "edit_datasource_btn"; //$NON-NLS-1$
private static final String REMOVE_DATASOURCES_BTN_ID = "remove_datasource_btn"; //$NON-NLS-1$
private static final String ELEMENTS_PROPERTY_NAME = "elements"; //$NON-NLS-1$
private static final String SELECTED_INDEX_PROPERTY_NAME = "selectedIndex"; //$NON-NLS-1$
private static final String SELECTED_ROWS_PROPERTY_NAME = "selectedRows"; //$NON-NLS-1$
private static final String SELECTED_ITEM_PROPERTY_NAME = "selectedItem"; //$NON-NLS-1$
private static final String CURRENT_QUERY_PROPERTY_NAME = "currentQuery"; //$NON-NLS-1$
private static final String DATASOURCES_ROOT_PROPERTY_NAME = "dataSourcesRoot"; //$NON-NLS-1$
private static final String VALUE_PROPERTY_NAME = "value"; //$NON-NLS-1$
private static final String ENABLED_PROPERTY_NAME = "!disabled"; //$NON-NLS-1$
private IndiciesToBooleanBindingConverter indiciesToBooleanBindingConverter;
private DatasourceModelNode dataSourcesRoot;
private List<XulEditorDataFactoryMetaData> dataFactoryMetas;
private CompoundDataFactory cdf;
public DataSourceAndQueryStep()
{
super();
indiciesToBooleanBindingConverter = new IndiciesToBooleanBindingConverter();
dataFactoryMetas = new ArrayList<XulEditorDataFactoryMetaData>();
refreshMetadata();
}
public DesignTimeContext getDesignTimeContext()
{
return designTimeContext;
}
public void setDesignTimeContext(final DesignTimeContext designTimeContext)
{
this.designTimeContext = designTimeContext;
refreshMetadata();
}
private void refreshMetadata()
{
final DataFactoryMetaData[] dfmdArray = DataFactoryRegistry.getInstance().getAll();
Arrays.sort(dfmdArray, new DataFactoryMetaDataComparator());
for (final DataFactoryMetaData dfmd : dfmdArray)
{
if (dfmd.isEditable() == false)
{
continue;
}
if (dfmd.isEditorAvailable() == false)
{
continue;
}
if (dfmd.isHidden())
{
continue;
}
dataFactoryMetas.add(new XulEditorDataFactoryMetaData(dfmd));
}
}
public void setBindings()
{
getBindingFactory().setBindingType(Type.ONE_WAY);
getBindingFactory().createBinding(this, DATASOURCES_ROOT_PROPERTY_NAME, DATASOURCES_TREE_ID, ELEMENTS_PROPERTY_NAME);
getBindingFactory().createBinding(DATASOURCES_TREE_ID, SELECTED_ITEM_PROPERTY_NAME, this, CURRENT_QUERY_PROPERTY_NAME, new CurrentQueryBindingConverter());
getBindingFactory().createBinding(DATASOURCES_TREE_ID, SELECTED_ROWS_PROPERTY_NAME, EDIT_DATASOURCES_BTN_ID, ENABLED_PROPERTY_NAME, indiciesToBooleanBindingConverter);
getBindingFactory().createBinding(DATASOURCES_TREE_ID, SELECTED_ROWS_PROPERTY_NAME, REMOVE_DATASOURCES_BTN_ID, ENABLED_PROPERTY_NAME, indiciesToBooleanBindingConverter);
}
public void editQuery(final String queryName)
{
final DataFactory dataFactory = getOwnerDataFactory(queryName);
final DataFactoryMetaData o = getMetaForDataFactory(dataFactory, dataFactoryMetas);
editOrCreateDataFactory(o);
}
private DataFactoryMetaData getMetaForDataFactory(final DataFactory dataFactory,
final List<XulEditorDataFactoryMetaData> metaDatas)
{
final String dfClassName = dataFactory.getClass().getName();
for (final XulEditorDataFactoryMetaData mdfmd : metaDatas)
{
final DataFactoryMetaData data = mdfmd.getMetadata();
final String mdFactoryName = data.getName();
if (dfClassName.equals(mdFactoryName))
{
return mdfmd.getMetadata();
}
}
return null;
}
private int getDataFactoryForMeta(final DataFactoryMetaData dfMetaData)
{
for (int i = 0; i < cdf.size(); i++)
{
final DataFactory df = cdf.getReference(i);
if (dfMetaData.getName().equals(df.getClass().getName()))
{
return i;
}
}
return -1;
}
private DataFactory getOwnerDataFactory(final String queryName)
{
return cdf.getDataFactoryForQuery(queryName);
}
public void createDataFactory()
{
final XulDialog datasourceType = (XulDialog) getDocument().getElementById(DATASOURCE_TYPE_DIALOG_ID);
datasourceType.setVisible(true);
final XulListbox box = (XulListbox) getDocument().getElementById(DATASOURCE_SELECTIONS_BOX_ID);
final XulEditorDataFactoryMetaData myEditData = (XulEditorDataFactoryMetaData) box.getSelectedItem();
if (myEditData != null)
{
editOrCreateDataFactory(myEditData.getMetadata());
}
box.setSelectedIndices(new int[0]); // clear the selection for next time.
}
public void editOrCreateDataFactory(final DataFactoryMetaData o)
{
if (o == null)
{
return;
}
if (o.isHidden())
{
return;
}
final DataFactory editDataFactory = getEditDataFactory(o);
final DataSourcePlugin dataSourcePlugin = o.createEditor();
final DataFactory generatedDataFactory = dataSourcePlugin.performEdit(getDesignTimeContext(), editDataFactory, null);
if (generatedDataFactory != null)
{
cdf.add(generatedDataFactory);
cdf = CompoundDataFactory.normalize(cdf);
updateDatasourceTree();
}
else
{ // user must have cancelled
if (editDataFactory != null)
{
cdf.add(editDataFactory);
}
}
setValid(validateStep());
}
/**
* @param o
* @return a DataFactory that matches the type of the DataFactoryMetaData (o) if it exists in the
* CompoundDataFactory (cdf), null if it doesn't exist.
*/
private DataFactory getEditDataFactory(final DataFactoryMetaData o)
{
final String mdfactoryName = o.getName();
for (int i = 0; i < cdf.size(); i++)
{
final DataFactory df = cdf.getReference(i);
final String dfClassName = df.getClass().getName();
if (mdfactoryName.equals(dfClassName))
{
cdf.remove(i);
return df;
}
}
return null;
}
public void stepActivating()
{
super.stepActivating();
cdf = (CompoundDataFactory) getEditorModel().getReportDefinition().getDataFactory();
updateDatasourceTree();
setValid(validateStep());
}
public boolean stepDeactivating()
{
getEditorModel().getReportDefinition().setDataFactory(cdf);
return super.stepDeactivating();
}
public void deleteDataFactory(final DataFactoryMetaData userObject)
{
final int datasourceIndex = getDataFactoryForMeta(userObject);
if (datasourceIndex >= 0)
{
cdf.remove(getDataFactoryForMeta(userObject));
}
}
public void deleteConnection(final DataFactory datafactory)
{
cdf.remove(datafactory);
}
/**
*
*/
private void updateDatasourceTree()
{
final DatasourceModelNode newRoot = new DatasourceModelNode(DATASOURCES_ROOT_NODE_NAME, null, DATASOURCE_TYPE.ROOT);
for (int i = 0; i < cdf.size(); i++)
{
final DataFactory df = cdf.getReference(i);
final DataFactoryMetaData dfmd = getMetaForDataFactory(df, dataFactoryMetas);
if (dfmd == null)
{
continue;
}
DatasourceModelNode dfmdNode = findUserObjectInTree(dfmd, newRoot);
if (dfmdNode == null)
{
dfmdNode = new DatasourceModelNode(dfmd.getDisplayName(Locale.getDefault()), dfmd, DATASOURCE_TYPE.DATAFACTORY);
newRoot.add(dfmdNode);
}
DatasourceModelNode dataSourceNode = null;
final String connectionName = dfmd.getDisplayConnectionName(df);
if (connectionName != null)
{
dataSourceNode = new DatasourceModelNode(connectionName, df, DATASOURCE_TYPE.CONNECTION);
}
if (dataSourceNode != null)
{
dfmdNode.add(dataSourceNode);
}
for (final String queryName : df.getQueryNames())
{
final DatasourceModelNode queryNode = new DatasourceModelNode(queryName, null, DATASOURCE_TYPE.QUERY);
if (dataSourceNode != null)
{
dataSourceNode.add(queryNode);
}
else
{
dfmdNode.add(queryNode);
}
}
}
this.setDataSourcesRoot(newRoot);
final XulTree tree = (XulTree) getDocument().getElementById(DATASOURCES_TREE_ID);
final String currentQuery = getCurrentQuery();
final int selectedQueryRow = findRowForObject(getDataSourcesRoot(), currentQuery, new int[]{0});
if (selectedQueryRow == -1)
{
final int[] selectedRows = new int[1];
selectedRows[0] = selectedQueryRow - 1; // have to subtract one for the (unshown) root
tree.setSelectedRows(selectedRows);
}
}
private int findRowForObject(final DatasourceModelNode startNode, final Object searchObj, final int[] index)
{
if (index == null || index.length != 1)
{
throw new IllegalArgumentException();
}
if (searchObj == null)
{
return -1;
}
// First try to match a user object if we have one
if (ObjectUtilities.equal(searchObj, startNode.getUserObject()))
{
return index[0];
}
// Otherwise check the children
for (final DatasourceModelNode node : startNode)
{
index[0] += 1;
final int result = findRowForObject(node, searchObj, index);
if (result != -1)
{
return result;
}
}
return -1;
}
private DatasourceModelNode findUserObjectInTree(final Object userObj, final DatasourceModelNode startNode)
{
if (userObj == null)
{
throw new NullPointerException("UserObject must not be null");
}
if (startNode == null)
{
throw new NullPointerException("StartNode must not be null");
}
if (userObj.equals(startNode.getUserObject()))
{
return startNode;
}
for (final DatasourceModelNode childNode : startNode)
{
final DatasourceModelNode found = findUserObjectInTree(userObj, childNode);
if (found != null)
{
return found;
}
}
return null;
}
protected boolean validateStep()
{
// If we have no createdDataFactory and we don't have anything in the model then we can't continue
final AbstractReportDefinition reportDefinition = getEditorModel().getReportDefinition();
if (reportDefinition.getDataFactory() == null ||
StringUtils.isEmpty(reportDefinition.getQuery()))
{
DebugLog.log("Have no query or no datafactory " +
reportDefinition.getDataFactory() + " " + reportDefinition.getQuery());
return false;
}
// if we have a DataFactory and a query make sure that they are contained in cdf.
final String queryName = reportDefinition.getQuery();
if (cdf.isQueryExecutable(queryName, new StaticDataRow()) == false)
{
return false;
}
try
{
final AbstractReportDefinition abstractReportDefinition =
(AbstractReportDefinition) reportDefinition.derive();
abstractReportDefinition.setDataFactory(cdf.derive());
final DataSchemaModel schemaModel = WizardEditorModel.compileDataSchemaModel(abstractReportDefinition);
return schemaModel.isValid();
}
catch (Exception ee)
{
getDesignTimeContext().userError(ee);
return false;
}
}
public void createPresentationComponent(final XulDomContainer mainWizardContainer) throws XulException
{
super.createPresentationComponent(mainWizardContainer);
mainWizardContainer.loadOverlay(DATASOURCE_AND_QUERY_STEP_OVERLAY);
mainWizardContainer.addEventHandler(new DatasourceAndQueryStepHandler());
final XulListbox box = (XulListbox) getDocument().getElementById(DATASOURCE_SELECTIONS_BOX_ID);
box.removeItems();
for (final XulEditorDataFactoryMetaData dfMeta : dataFactoryMetas)
{
box.addItem(dfMeta);
}
box.addPropertyChangeListener(new SelectedIndexUpdateHandler());
}
public List<XulEditorDataFactoryMetaData> getDataFactoryMetas()
{
return dataFactoryMetas;
}
public void setDataFactoryMetas(final ArrayList<XulEditorDataFactoryMetaData> datas)
{
this.dataFactoryMetas = datas;
}
public String getCurrentQuery()
{
return getEditorModel().getReportDefinition().getQuery();
}
public void setCurrentQuery(final String currentQuery)
{
final String oldQuery = getCurrentQuery();
if (!(currentQuery != null && currentQuery.equals(oldQuery)))
{
getEditorModel().getReportDefinition().setQuery(currentQuery);
this.firePropertyChange(CURRENT_QUERY_PROPERTY_NAME, oldQuery, currentQuery);
this.setValid(validateStep());
}
}
public DatasourceModelNode getDataSourcesRoot()
{
return dataSourcesRoot;
}
public void setDataSourcesRoot(final DatasourceModelNode dataSourcesRoot)
{
final DatasourceModelNode oldDataSourcesRoot = this.dataSourcesRoot;
this.dataSourcesRoot = dataSourcesRoot;
this.firePropertyChange(DATASOURCES_ROOT_PROPERTY_NAME, oldDataSourcesRoot, dataSourcesRoot);
}
/* (non-Javadoc)
* @see org.pentaho.reporting.engine.classic.wizard.ui.xul.components.WizardStep#getStepName()
*/
public String getStepName()
{
return messages.getString("DATASOURCE_AND_QUERY_STEP.Step_Name"); //$NON-NLS-1$
}
}