/*! * 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-2017 Pentaho Corporation.. All rights reserved. */ package org.pentaho.reporting.engine.classic.extensions.datasources.kettle; import org.pentaho.di.core.exception.KettleException; import org.pentaho.di.core.parameters.UnknownParamException; import org.pentaho.di.core.plugins.PluginRegistry; import org.pentaho.di.core.plugins.RepositoryPluginType; import org.pentaho.di.core.row.RowMetaInterface; import org.pentaho.di.repository.RepositoriesMeta; import org.pentaho.di.repository.Repository; import org.pentaho.di.repository.RepositoryMeta; import org.pentaho.di.trans.Trans; import org.pentaho.di.trans.TransMeta; import org.pentaho.di.trans.step.StepInterface; import org.pentaho.di.trans.step.StepMeta; import org.pentaho.di.trans.step.StepMetaDataCombi; import org.pentaho.reporting.engine.classic.core.DataFactory; import org.pentaho.reporting.engine.classic.core.DataFactoryContext; import org.pentaho.reporting.engine.classic.core.DataRow; import org.pentaho.reporting.engine.classic.core.ParameterMapping; import org.pentaho.reporting.engine.classic.core.ReportDataFactoryException; import org.pentaho.reporting.libraries.base.util.ArgumentNullException; import org.pentaho.reporting.libraries.formula.EvaluationException; import org.pentaho.reporting.libraries.formula.FormulaContext; import org.pentaho.reporting.libraries.formula.parser.ParseException; import org.pentaho.reporting.libraries.resourceloader.ResourceKey; import org.pentaho.reporting.libraries.resourceloader.ResourceManager; import javax.swing.table.TableModel; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; public abstract class AbstractKettleTransformationProducer implements KettleTransformationProducer { private static final long serialVersionUID = -2287953597208384424L; private String stepName; private String username; private String password; private String repositoryName; private FormulaArgument[] arguments; private FormulaParameter[] parameter; private transient Trans currentlyRunningTransformation; private boolean stopOnError; @Deprecated public AbstractKettleTransformationProducer( final String repositoryName, final String stepName, final String username, final String password, final String[] definedArgumentNames, final ParameterMapping[] definedVariableNames ) { this( repositoryName, stepName, username, password, FormulaArgument.convert( definedArgumentNames ), FormulaParameter.convert( definedVariableNames ) ); } protected AbstractKettleTransformationProducer( final String repositoryName, final String stepName, final String username, final String password, final FormulaArgument[] definedArgumentNames, final FormulaParameter[] definedVariableNames ) { if ( repositoryName == null ) { throw new NullPointerException(); } if ( definedArgumentNames == null ) { throw new NullPointerException(); } if ( definedVariableNames == null ) { throw new NullPointerException(); } this.stopOnError = true; this.repositoryName = repositoryName; this.stepName = stepName; this.username = username; this.password = password; this.arguments = definedArgumentNames.clone(); this.parameter = definedVariableNames.clone(); } public boolean isStopOnError() { return stopOnError; } public void setStopOnError( final boolean stopOnError ) { this.stopOnError = stopOnError; } public String getStepName() { return stepName; } public String getUsername() { return username; } public String getPassword() { return password; } public String getRepositoryName() { return repositoryName; } public String[] getDefinedArgumentNames() { return FormulaArgument.convert( arguments ); } public ParameterMapping[] getDefinedVariableNames() { return FormulaParameter.convert( parameter ); } public FormulaArgument[] getArguments() { return arguments.clone(); } public FormulaParameter[] getParameter() { return parameter.clone(); } public Object clone() { try { final AbstractKettleTransformationProducer prod = (AbstractKettleTransformationProducer) super.clone(); prod.arguments = arguments.clone(); prod.parameter = parameter.clone(); prod.currentlyRunningTransformation = null; return prod; } catch ( final CloneNotSupportedException e ) { throw new IllegalStateException( e ); } } public TransMeta loadTransformation( final DataFactoryContext context ) throws KettleException, ReportDataFactoryException { final Repository repository = connectToRepository(); try { return loadTransformation( repository, context.getResourceManager(), context.getContextKey() ); } finally { currentlyRunningTransformation = null; if ( repository != null ) { repository.disconnect(); } } } public TableModel queryDesignTimeStructure( final DataRow parameter, final DataFactoryContext context ) throws ReportDataFactoryException, KettleException { String stepName = getStepName(); if ( stepName == null ) { throw new ReportDataFactoryException( "No step name defined." ); } final Repository repository = connectToRepository(); try { ResourceManager resourceManager = context.getResourceManager(); final TransMeta transMeta = loadTransformation( repository, resourceManager, context.getContextKey() ); if ( isDynamicTransformation( transMeta ) ) { // we cannot safely guess columns from transformations that use Metadata-Injection. // So lets solve them the traditional way. return performQueryOnTransformation( parameter, 1, context, transMeta ); } StepMeta step = transMeta.findStep( stepName ); if ( step == null ) { throw new ReportDataFactoryException( "Cannot find the specified transformation step " + stepName ); } final RowMetaInterface row = transMeta.getStepFields( getStepName() ); final TableProducer tableProducer = new TableProducer( row, 1, true ); return tableProducer.getTableModel(); } catch ( final EvaluationException e ) { throw new ReportDataFactoryException( "Failed to evaluate parameter", e ); } catch ( final ParseException e ) { throw new ReportDataFactoryException( "Failed to evaluate parameter", e ); } finally { if ( repository != null ) { repository.disconnect(); } } } private boolean isDynamicTransformation( final TransMeta transMeta ) { List<StepMeta> steps = transMeta.getSteps(); for ( final StepMeta step : steps ) { if ( step.isEtlMetaInject() ) { return true; } } return false; } public TableModel performQuery( final DataRow parameters, final int queryLimit, final DataFactoryContext context ) throws KettleException, ReportDataFactoryException { ArgumentNullException.validate( "context", context ); ArgumentNullException.validate( "parameters", parameters ); String targetStepName = getStepName(); if ( targetStepName == null ) { throw new ReportDataFactoryException( "No step name defined." ); } final Repository repository = connectToRepository(); try { final TransMeta transMeta = loadTransformation( repository, context.getResourceManager(), context.getContextKey() ); return performQueryOnTransformation( parameters, queryLimit, context, transMeta ); } catch ( final EvaluationException e ) { throw new ReportDataFactoryException( "Failed to evaluate parameter", e ); } catch ( final ParseException e ) { throw new ReportDataFactoryException( "Failed to evaluate parameter", e ); } finally { if ( repository != null ) { repository.disconnect(); } } } protected TableModel performQueryOnTransformation( final DataRow parameters, final int queryLimit, final DataFactoryContext context, final TransMeta transMeta ) throws EvaluationException, ParseException, KettleException, ReportDataFactoryException { final Trans trans = prepareTransformation( parameters, context, transMeta ); StepInterface targetStep = findTargetStep( trans ); final RowMetaInterface row = transMeta.getStepFields( getStepName() ); TableProducer tableProducer = new TableProducer( row, queryLimit, isStopOnError() ); targetStep.addRowListener( tableProducer ); currentlyRunningTransformation = trans; try { trans.startThreads(); trans.waitUntilFinished(); } finally { trans.cleanup(); currentlyRunningTransformation = null; } if ( trans.getErrors() != 0 && isStopOnError() ) { throw new ReportDataFactoryException( String .format( "Transformation reported %d records with errors and stop-on-error is true. Aborting.", trans.getErrors() ) ); } return tableProducer.getTableModel(); } private Trans prepareTransformation( final DataRow parameters, final DataFactoryContext context, final TransMeta transMeta ) throws EvaluationException, ParseException, KettleException { final FormulaContext formulaContext = new WrappingFormulaContext( context.getFormulaContext(), parameters ); final String[] params = fillArguments( formulaContext ); final Trans trans = new Trans( transMeta ); trans.setArguments( params ); updateTransformationParameter( formulaContext, trans ); transMeta.setInternalKettleVariables(); trans.prepareExecution( params ); return trans; } private StepInterface findTargetStep( final Trans trans ) throws ReportDataFactoryException { final String stepName = getStepName(); final List<StepMetaDataCombi> stepList = trans.getSteps(); for ( int i = 0; i < stepList.size(); i++ ) { final StepMetaDataCombi metaDataCombi = stepList.get( i ); if ( stepName.equals( metaDataCombi.stepname ) ) { return metaDataCombi.step; } } throw new ReportDataFactoryException( "Cannot find the specified transformation step " + stepName ); } private void updateTransformationParameter( final FormulaContext formulaContext, final Trans trans ) throws UnknownParamException, EvaluationException, ParseException { for ( int i = 0; i < this.parameter.length; i++ ) { final FormulaParameter mapping = this.parameter[ i ]; final String sourceName = mapping.getName(); final Object value = mapping.compute( formulaContext ); if ( value != null ) { trans.setParameterValue( sourceName, String.valueOf( value ) ); } else { trans.setParameterValue( sourceName, null ); } } } private String[] fillArguments( final FormulaContext context ) throws EvaluationException, ParseException { final String[] params = new String[ arguments.length ]; for ( int i = 0; i < arguments.length; i++ ) { final FormulaArgument arg = arguments[ i ]; Object compute = arg.compute( context ); if ( compute == null ) { params[ i ] = null; } else { params[ i ] = String.valueOf( compute ); } } return params; } private Repository connectToRepository() throws ReportDataFactoryException, KettleException { if ( repositoryName == null ) { throw new NullPointerException(); } final RepositoriesMeta repositoriesMeta = new RepositoriesMeta(); try { repositoriesMeta.readData(); } catch ( final KettleException ke ) { // we're a bit low to bubble a dialog to the user here.. // when ramaiz fixes readData() to stop throwing exceptions // even when successful we can remove this and use // the more favorable repositoriesMeta.getException() or something // like it (I'm guessing on the method name) } // Find the specified repository. final RepositoryMeta repositoryMeta = repositoriesMeta.findRepository( repositoryName ); if ( repositoryMeta == null ) { // repository object is not necessary for filesystem transformations return null; // throw new ReportDataFactoryException("The specified repository " + repositoryName + " is not defined."); } final Repository repository = PluginRegistry.getInstance().loadClass( RepositoryPluginType.class, repositoryMeta.getId(), Repository.class ); repository.init( repositoryMeta ); repository.connect( username, password ); return repository; } protected abstract TransMeta loadTransformation( Repository repository, ResourceManager resourceManager, ResourceKey contextKey ) throws ReportDataFactoryException, KettleException; public void cancelQuery() { final Trans currentlyRunningTransformation = this.currentlyRunningTransformation; if ( currentlyRunningTransformation != null ) { currentlyRunningTransformation.stopAll(); this.currentlyRunningTransformation = null; } } public String[] getReferencedFields() throws ParseException { final LinkedHashSet<String> retval = new LinkedHashSet<String>(); HashSet<String> args = new HashSet<String>(); for ( final FormulaArgument argument : arguments ) { args.addAll( Arrays.asList( argument.getReferencedFields() ) ); } for ( final FormulaParameter formulaParameter : parameter ) { args.addAll( Arrays.asList( formulaParameter.getReferencedFields() ) ); } retval.addAll( args ); retval.add( DataFactory.QUERY_LIMIT ); return retval.toArray( new String[ retval.size() ] ); } protected ArrayList<Object> internalGetQueryHash() { final ArrayList<Object> retval = new ArrayList<Object>(); retval.add( getClass().getName() ); retval.add( getUsername() ); retval.add( getPassword() ); retval.add( getStepName() ); retval.add( isStopOnError() ); retval.add( getRepositoryName() ); retval.add( new ArrayList<FormulaArgument>( Arrays.asList( getArguments() ) ) ); retval.add( new ArrayList<FormulaParameter>( Arrays.asList( getParameter() ) ) ); return retval; } protected String computeFullFilename( ResourceKey key ) { while ( key != null ) { final Object identifier = key.getIdentifier(); if ( identifier instanceof File ) { final File file = (File) identifier; return file.getAbsolutePath(); } key = key.getParent(); } return null; } }