/*! ******************************************************************************
*
* Pentaho Data Integration
*
* Copyright (C) 2002-2013 by Pentaho : http://www.pentaho.com
*
*******************************************************************************
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
package org.pentaho.di.trans.steps.javafilter;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.codehaus.janino.ExpressionEvaluator;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.exception.KettleValueException;
import org.pentaho.di.core.row.RowMetaInterface;
import org.pentaho.di.core.row.ValueMetaInterface;
import org.pentaho.di.i18n.BaseMessages;
import org.pentaho.di.trans.Trans;
import org.pentaho.di.trans.TransMeta;
import org.pentaho.di.trans.step.BaseStep;
import org.pentaho.di.trans.step.StepDataInterface;
import org.pentaho.di.trans.step.StepInterface;
import org.pentaho.di.trans.step.StepMeta;
import org.pentaho.di.trans.step.StepMetaInterface;
import org.pentaho.di.trans.step.errorhandling.StreamInterface;
/**
* Calculate new field values using pre-defined functions.
*
* @author Matt
* @since 8-sep-2005
*/
public class JavaFilter extends BaseStep implements StepInterface {
private static Class<?> PKG = JavaFilterMeta.class; // for i18n purposes, needed by Translator2!!
private JavaFilterMeta meta;
private JavaFilterData data;
public JavaFilter( StepMeta stepMeta, StepDataInterface stepDataInterface, int copyNr, TransMeta transMeta,
Trans trans ) {
super( stepMeta, stepDataInterface, copyNr, transMeta, trans );
}
public boolean processRow( StepMetaInterface smi, StepDataInterface sdi ) throws KettleException {
meta = (JavaFilterMeta) smi;
data = (JavaFilterData) sdi;
Object[] r = getRow(); // get row, set busy!
if ( r == null ) { // no more input to be expected...
setOutputDone();
return false;
}
if ( first ) {
first = false;
data.outputRowMeta = getInputRowMeta().clone();
meta.getFields( data.outputRowMeta, getStepname(), null, null, this, repository, metaStore );
// Cache the position of the RowSet for the output.
//
if ( data.chosesTargetSteps ) {
List<StreamInterface> targetStreams = meta.getStepIOMeta().getTargetStreams();
data.trueRowSet = findOutputRowSet( getStepname(), getCopy(), targetStreams.get( 0 ).getStepname(), 0 );
if ( data.trueRowSet == null ) {
throw new KettleException( BaseMessages.getString(
PKG, "JavaFilter.Log.TargetStepInvalid", targetStreams.get( 0 ).getStepname() ) );
}
data.falseRowSet = findOutputRowSet( getStepname(), getCopy(), targetStreams.get( 1 ).getStepname(), 0 );
if ( data.falseRowSet == null ) {
throw new KettleException( BaseMessages.getString(
PKG, "JavaFilter.Log.TargetStepInvalid", targetStreams.get( 1 ).getStepname() ) );
}
}
}
if ( log.isRowLevel() ) {
logRowlevel( "Read row #" + getLinesRead() + " : " + getInputRowMeta().getString( r ) );
}
boolean keep = calcFields( getInputRowMeta(), r );
if ( !data.chosesTargetSteps ) {
if ( keep ) {
putRow( data.outputRowMeta, r ); // copy row to output rowset(s);
}
} else {
if ( keep ) {
if ( log.isRowLevel() ) {
logRowlevel( "Sending row to true :" + data.trueStepname + " : " + getInputRowMeta().getString( r ) );
}
putRowTo( data.outputRowMeta, r, data.trueRowSet );
} else {
if ( log.isRowLevel() ) {
logRowlevel( "Sending row to false :" + data.falseStepname + " : " + getInputRowMeta().getString( r ) );
}
putRowTo( data.outputRowMeta, r, data.falseRowSet );
}
}
if ( checkFeedback( getLinesRead() ) ) {
if ( log.isBasic() ) {
logBasic( BaseMessages.getString( PKG, "JavaFilter.Log.LineNumber" ) + getLinesRead() );
}
}
return true;
}
private boolean calcFields( RowMetaInterface rowMeta, Object[] r ) throws KettleValueException {
try {
// Initialize evaluators etc. Only do it once.
//
if ( data.expressionEvaluator == null ) {
String realCondition = environmentSubstitute( meta.getCondition() );
data.argumentIndexes = new ArrayList<Integer>();
List<String> parameterNames = new ArrayList<String>();
List<Class<?>> parameterTypes = new ArrayList<Class<?>>();
for ( int i = 0; i < data.outputRowMeta.size(); i++ ) {
ValueMetaInterface valueMeta = data.outputRowMeta.getValueMeta( i );
// See if the value is being used in a formula...
//
if ( realCondition.contains( valueMeta.getName() ) ) {
// If so, add it to the indexes...
data.argumentIndexes.add( i );
Class<?> parameterType;
switch ( valueMeta.getType() ) {
case ValueMetaInterface.TYPE_STRING:
parameterType = String.class;
break;
case ValueMetaInterface.TYPE_NUMBER:
parameterType = Double.class;
break;
case ValueMetaInterface.TYPE_INTEGER:
parameterType = Long.class;
break;
case ValueMetaInterface.TYPE_DATE:
parameterType = Date.class;
break;
case ValueMetaInterface.TYPE_BIGNUMBER:
parameterType = BigDecimal.class;
break;
case ValueMetaInterface.TYPE_BOOLEAN:
parameterType = Boolean.class;
break;
case ValueMetaInterface.TYPE_BINARY:
parameterType = byte[].class;
break;
default:
parameterType = String.class;
break;
}
parameterTypes.add( parameterType );
parameterNames.add( valueMeta.getName() );
}
}
// Create the expression evaluator: is relatively slow so we do it only for the first row...
//
data.expressionEvaluator = new ExpressionEvaluator();
data.expressionEvaluator.setParameters(
parameterNames.toArray( new String[parameterNames.size()] ), parameterTypes
.toArray( new Class<?>[parameterTypes.size()] ) );
data.expressionEvaluator.setReturnType( Object.class );
data.expressionEvaluator.setThrownExceptions( new Class<?>[] { Exception.class } );
data.expressionEvaluator.cook( realCondition );
// Also create the argument data structure once...
//
data.argumentData = new Object[data.argumentIndexes.size()];
}
// This method can only accept the specified number of values...
//
for ( int x = 0; x < data.argumentIndexes.size(); x++ ) {
int index = data.argumentIndexes.get( x );
ValueMetaInterface outputValueMeta = data.outputRowMeta.getValueMeta( index );
data.argumentData[x] = outputValueMeta.convertToNormalStorageType( r[index] );
}
Object formulaResult = data.expressionEvaluator.evaluate( data.argumentData );
if ( formulaResult instanceof Boolean ) {
return (Boolean) formulaResult;
} else {
throw new KettleException( "The result of the filter expression must be a boolean and we got back : "
+ formulaResult.getClass().getName() );
}
} catch ( Exception e ) {
throw new KettleValueException( e );
}
}
public boolean init( StepMetaInterface smi, StepDataInterface sdi ) {
meta = (JavaFilterMeta) smi;
data = (JavaFilterData) sdi;
if ( super.init( smi, sdi ) ) {
List<StreamInterface> targetStreams = meta.getStepIOMeta().getTargetStreams();
data.trueStepname = targetStreams.get( 0 ).getStepname();
data.falseStepname = targetStreams.get( 1 ).getStepname();
if ( targetStreams.get( 0 ).getStepMeta() != null ^ targetStreams.get( 1 ).getStepMeta() != null ) {
logError( BaseMessages.getString( PKG, "JavaFilter.Log.BothTrueAndFalseNeeded" ) );
} else {
data.chosesTargetSteps =
targetStreams.get( 0 ).getStepMeta() != null && targetStreams.get( 1 ).getStepMeta() != null;
return true;
}
}
return false;
}
}