/*! ****************************************************************************** * * Pentaho Data Integration * * Copyright (C) 2002-2016 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.janino; import java.util.ArrayList; import java.util.List; import org.codehaus.janino.ExpressionEvaluator; import org.pentaho.di.core.Const; import org.pentaho.di.core.util.Utils; import org.pentaho.di.core.exception.KettleException; import org.pentaho.di.core.exception.KettleValueException; import org.pentaho.di.core.row.RowDataUtil; import org.pentaho.di.core.row.RowMetaInterface; import org.pentaho.di.core.row.ValueMetaInterface; import org.pentaho.di.core.row.value.ValueMetaFactory; 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; /** * Calculate new field values using pre-defined functions. * * @author Matt * @since 8-sep-2005 */ public class Janino extends BaseStep implements StepInterface { private static Class<?> PKG = JaninoMeta.class; private JaninoMeta meta; private JaninoData data; public Janino( StepMeta stepMeta, StepDataInterface stepDataInterface, int copyNr, TransMeta transMeta, Trans trans ) { super( stepMeta, stepDataInterface, copyNr, transMeta, trans ); } @Override public boolean processRow( StepMetaInterface smi, StepDataInterface sdi ) throws KettleException { meta = (JaninoMeta) smi; data = (JaninoData) 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 ); // Calculate replace indexes... // data.replaceIndex = new int[meta.getFormula().length]; data.returnType = new ValueMetaInterface[meta.getFormula().length]; for ( int i = 0; i < meta.getFormula().length; i++ ) { JaninoMetaFunction fn = meta.getFormula()[i]; data.returnType[i] = ValueMetaFactory.createValueMeta( fn.getValueType() ); if ( !Utils.isEmpty( fn.getReplaceField() ) ) { data.replaceIndex[i] = getInputRowMeta().indexOfValue( fn.getReplaceField() ); if ( data.replaceIndex[i] < 0 ) { throw new KettleException( "Unknown field specified to replace with a formula result: [" + fn.getReplaceField() + "]" ); } } else { data.replaceIndex[i] = -1; } } } if ( log.isRowLevel() ) { logRowlevel( "Read row #" + getLinesRead() + " : " + getInputRowMeta().getString( r ) ); } try { Object[] outputRowData = calcFields( getInputRowMeta(), r ); putRow( data.outputRowMeta, outputRowData ); // copy row to possible alternate rowset(s). if ( log.isRowLevel() ) { logRowlevel( "Wrote row #" + getLinesWritten() + " : " + data.outputRowMeta.getString( outputRowData ) ); } } catch ( Exception e ) { if ( getStepMeta().isDoingErrorHandling() ) { putError( getInputRowMeta(), r, 1L, e.toString(), null, "UJE001" ); } else { throw new KettleException( e ); } } if ( checkFeedback( getLinesRead() ) ) { logBasic( "Linenr " + getLinesRead() ); } return true; } private Object[] calcFields( RowMetaInterface rowMeta, Object[] r ) throws KettleValueException { try { Object[] outputRowData = RowDataUtil.createResizedCopy( r, data.outputRowMeta.size() ); int tempIndex = rowMeta.size(); // Initialize evaluators etc. Only do it once. // if ( data.expressionEvaluators == null ) { data.expressionEvaluators = new ExpressionEvaluator[meta.getFormula().length]; data.argumentIndexes = new ArrayList<List<Integer>>(); for ( int i = 0; i < meta.getFormula().length; i++ ) { List<Integer> argIndexes = new ArrayList<Integer>(); data.argumentIndexes.add( argIndexes ); } for ( int m = 0; m < meta.getFormula().length; m++ ) { List<Integer> argIndexes = data.argumentIndexes.get( m ); 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 ( meta.getFormula()[m].getFormula().contains( valueMeta.getName() ) ) { // If so, add it to the indexes... argIndexes.add( i ); parameterTypes.add( valueMeta.getNativeDataTypeClass() ); parameterNames.add( valueMeta.getName() ); } } JaninoMetaFunction fn = meta.getFormula()[m]; if ( !Utils.isEmpty( fn.getFieldName() ) ) { // Create the expression evaluator: is relatively slow so we do it only for the first row... // data.expressionEvaluators[m] = new ExpressionEvaluator(); data.expressionEvaluators[m].setParameters( parameterNames.toArray( new String[parameterNames.size()] ), parameterTypes .toArray( new Class<?>[parameterTypes.size()] ) ); data.expressionEvaluators[m].setReturnType( Object.class ); data.expressionEvaluators[m].setThrownExceptions( new Class<?>[] { Exception.class } ); data.expressionEvaluators[m].cook( fn.getFormula() ); } else { throw new KettleException( "Unable to find field name for formula [" + Const.NVL( fn.getFormula(), "" ) + "]" ); } } } for ( int i = 0; i < meta.getFormula().length; i++ ) { List<Integer> argumentIndexes = data.argumentIndexes.get( i ); // This method can only accept the specified number of values... // Object[] argumentData = new Object[argumentIndexes.size()]; for ( int x = 0; x < argumentIndexes.size(); x++ ) { int index = argumentIndexes.get( x ); ValueMetaInterface outputValueMeta = data.outputRowMeta.getValueMeta( index ); argumentData[x] = outputValueMeta.convertToNormalStorageType( outputRowData[index] ); } Object formulaResult = data.expressionEvaluators[i].evaluate( argumentData ); Object value = null; if ( formulaResult == null ) { value = null; } else { ValueMetaInterface valueMeta = data.returnType[i]; if ( valueMeta.getNativeDataTypeClass().isAssignableFrom( formulaResult.getClass() ) ) { value = formulaResult; } else if ( formulaResult instanceof Integer && valueMeta.getType() == ValueMetaInterface.TYPE_INTEGER ) { value = ( (Integer) formulaResult ).longValue(); } else { throw new KettleValueException( BaseMessages.getString( PKG, "Janino.Error.ValueTypeMismatch", valueMeta.getTypeDesc(), meta.getFormula()[i].getFieldName(), formulaResult.getClass(), meta.getFormula()[i].getFormula() ) ); } } // We're done, store it in the row with all the data, including the temporary data... // if ( data.replaceIndex[i] < 0 ) { outputRowData[tempIndex++] = value; } else { outputRowData[data.replaceIndex[i]] = value; } } return outputRowData; } catch ( Exception e ) { throw new KettleValueException( e ); } } @Override public boolean init( StepMetaInterface smi, StepDataInterface sdi ) { meta = (JaninoMeta) smi; data = (JaninoData) sdi; return super.init( smi, sdi ); } }