/*! * 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.reporting.engine.classic.wizard.ui.xul.steps; 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.StaticDataRow; import org.pentaho.reporting.engine.classic.core.designtime.DataFactoryChange; import org.pentaho.reporting.engine.classic.core.designtime.DataSourcePlugin; import org.pentaho.reporting.engine.classic.core.designtime.DefaultDataFactoryChangeRecorder; 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.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; 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; 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 DefaultDataFactoryChangeRecorder changeRecorder = new DefaultDataFactoryChangeRecorder(); final DataFactory editDataFactory = grabAndRemoveEditDataFactory( o ); final DataSourcePlugin dataSourcePlugin = o.createEditor(); final DataFactory generatedDataFactory = dataSourcePlugin.performEdit( getDesignTimeContext(), editDataFactory, null, changeRecorder ); if ( generatedDataFactory != null ) { final DataFactoryChange[] changes = changeRecorder.getChanges(); DefaultDataFactoryChangeRecorder.applyChanges( cdf, changes ); 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 grabAndRemoveEditDataFactory( 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$ } }