org.pentaho.di.trans.steps.script.Script Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kettle-engine Show documentation
Show all versions of kettle-engine Show documentation
Container pom for Pentaho Data Integration modules
The newest version!
/*! ******************************************************************************
*
* Pentaho Data Integration
*
* Copyright (C) 2002-2017 by Hitachi Vantara : 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.script;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.ScriptContext;
import javax.script.ScriptException;
import org.pentaho.di.compatibility.Value;
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.KettleStepException;
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.util.JavaScriptUtils;
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;
/**
* Executes a JavaScript on the values in the input stream. Selected calculated values can then be put on the output
* stream.
*
* @author Matt
* @since 5-April-2003
*/
public class Script extends BaseStep implements StepInterface {
private static Class> PKG = ScriptMeta.class; // for i18n purposes, needed by Translator2!!
private ScriptMeta meta;
private ScriptData data;
public static final int SKIP_TRANSFORMATION = 1;
public static final int ABORT_TRANSFORMATION = -1;
public static final int ERROR_TRANSFORMATION = -2;
public static final int CONTINUE_TRANSFORMATION = 0;
private boolean bWithTransStat = false;
private boolean bRC = false;
private int iTranStat = CONTINUE_TRANSFORMATION;
private boolean bFirstRun = false;
private ScriptValuesScript[] jsScripts;
private String strTransformScript = "";
private String strStartScript = "";
private String strEndScript = "";
// public static Row insertRow;
// public String script; //TODO AKRETION should be compiled script actually
public Script( StepMeta stepMeta, StepDataInterface stepDataInterface, int copyNr, TransMeta transMeta,
Trans trans ) {
super( stepMeta, stepDataInterface, copyNr, transMeta, trans );
}
private void determineUsedFields( RowMetaInterface row ) {
int nr = 0;
// Count the occurrences of the values.
// Perhaps we find values in comments, but we take no risk!
//
for ( int i = 0; i < row.size(); i++ ) {
String valname = row.getValueMeta( i ).getName().toUpperCase();
if ( strTransformScript.toUpperCase().indexOf( valname ) >= 0 ) {
nr++;
}
}
// Allocate fields_used
data.fields_used = new int[ nr ];
data.values_used = new Value[ nr ];
nr = 0;
// Count the occurrences of the values.
// Perhaps we find values in comments, but we take no risk!
//
for ( int i = 0; i < row.size(); i++ ) {
// Values are case-insensitive in JavaScript.
//
String valname = row.getValueMeta( i ).getName();
if ( strTransformScript.indexOf( valname ) >= 0 ) {
if ( log.isDetailed() ) {
logDetailed( BaseMessages.getString( PKG, "Script.Log.UsedValueName", String.valueOf( i ), valname ) );
}
data.fields_used[ nr ] = i;
nr++;
}
}
if ( log.isDetailed() ) {
logDetailed( BaseMessages.getString( PKG, "Script.Log.UsingValuesFromInputStream", String
.valueOf( data.fields_used.length ) ) );
}
}
private boolean addValues( RowMetaInterface rowMeta, Object[] row ) throws KettleException {
if ( first ) {
first = false;
// What is the output row looking like?
//
data.outputRowMeta = getInputRowMeta().clone();
meta.getFields( data.outputRowMeta, getStepname(), null, null, this, repository, metaStore );
// Determine the indexes of the fields used!
//
determineUsedFields( rowMeta );
// Get the indexes of the replaced fields...
//
data.replaceIndex = new int[ meta.getFieldname().length ];
for ( int i = 0; i < meta.getFieldname().length; i++ ) {
if ( meta.getReplace()[ i ] ) {
data.replaceIndex[ i ] = rowMeta.indexOfValue( meta.getFieldname()[ i ] );
if ( data.replaceIndex[ i ] < 0 ) {
if ( Utils.isEmpty( meta.getFieldname()[ i ] ) ) {
throw new KettleStepException( BaseMessages.getString(
PKG, "ScriptValuesMetaMod.Exception.FieldToReplaceNotFound", meta.getFieldname()[ i ] ) );
}
data.replaceIndex[ i ] = rowMeta.indexOfValue( meta.getRename()[ i ] );
if ( data.replaceIndex[ i ] < 0 ) {
throw new KettleStepException( BaseMessages.getString(
PKG, "ScriptValuesMetaMod.Exception.FieldToReplaceNotFound", meta.getRename()[ i ] ) );
}
}
} else {
data.replaceIndex[ i ] = -1;
}
}
data.cx = ScriptMeta.createNewScriptEngine( getStepname() );
data.scope = data.cx.getBindings( ScriptContext.ENGINE_SCOPE );
bFirstRun = true;
data.scope.put( "_step_", this );
// Adding the existing Scripts to the Context
//
for ( int i = 0; i < meta.getNumberOfJSScripts(); i++ ) {
data.scope.put( jsScripts[ i ].getScriptName(), jsScripts[ i ].getScript() );
}
// Adding the Name of the Transformation to the Context
//
data.scope.put( "_TransformationName_", this.getStepname() );
try {
// add these now (they will be re-added later) to make
// compilation succeed
//
// Add the old style row object for compatibility reasons...
//
data.scope.put( "row", row );
// Add the used fields...
//
for ( int i = 0; i < data.fields_used.length; i++ ) {
ValueMetaInterface valueMeta = rowMeta.getValueMeta( data.fields_used[ i ] );
Object valueData = row[ data.fields_used[ i ] ];
Object normalStorageValueData = valueMeta.convertToNormalStorageType( valueData );
data.scope.put( valueMeta.getName(), normalStorageValueData );
}
// also add the meta information for the whole row
//
data.scope.put( "rowMeta", rowMeta );
// Modification for Additional Script parsing
//
try {
if ( meta.getAddClasses() != null ) {
for ( int i = 0; i < meta.getAddClasses().length; i++ ) {
// TODO AKRETION ensure it works
data.scope.put( meta.getAddClasses()[ i ].getJSName(), meta.getAddClasses()[ i ].getAddObject() );
// Object jsOut =
// Context.javaToJS(meta.getAddClasses()[i].getAddObject(),
// data.scope);
// ScriptableObject.putProperty(data.scope,
// meta.getAddClasses()[i].getJSName(), jsOut);
}
}
} catch ( Exception e ) {
throw new KettleValueException( BaseMessages.getString(
PKG, "Script.Log.CouldNotAttachAdditionalScripts" ), e );
}
// Adding some default JavaScriptFunctions to the System
// TODO AKRETION not implemented yet
// try {
// Context.javaToJS(ScriptValuesAddedFunctions.class,
// data.scope);
// ((ScriptableObject)
// data.scope).defineFunctionProperties(ScriptValuesAddedFunctions.jsFunctionList,
// ScriptValuesAddedFunctions.class, ScriptableObject.DONTENUM);
// } catch (Exception ex) {
// // System.out.println(ex.toString());
// throw new KettleValueException(BaseMessages.getString(PKG, "Script.Log.CouldNotAddDefaultFunctions"), ex);
// }
// ;
// Adding some Constants to the JavaScript
try {
data.scope.put( "SKIP_TRANSFORMATION", Integer.valueOf( SKIP_TRANSFORMATION ) );
data.scope.put( "ABORT_TRANSFORMATION", Integer.valueOf( ABORT_TRANSFORMATION ) );
data.scope.put( "ERROR_TRANSFORMATION", Integer.valueOf( ERROR_TRANSFORMATION ) );
data.scope.put( "CONTINUE_TRANSFORMATION", Integer.valueOf( CONTINUE_TRANSFORMATION ) );
} catch ( Exception ex ) {
// System.out.println("Exception Adding the Constants " +
// ex.toString());
throw new KettleValueException(
BaseMessages.getString( PKG, "Script.Log.CouldNotAddDefaultConstants" ), ex );
}
try {
// Checking for StartScript
if ( strStartScript != null && strStartScript.length() > 0 ) {
CompiledScript startScript = ( (Compilable) data.cx ).compile( strStartScript );
startScript.eval( data.scope );
if ( log.isDetailed() ) {
logDetailed( ( "Start Script found!" ) );
}
} else {
if ( log.isDetailed() ) {
logDetailed( ( "No starting Script found!" ) );
}
}
} catch ( Exception es ) {
// System.out.println("Exception processing StartScript " +
// es.toString());
throw new KettleValueException(
BaseMessages.getString( PKG, "Script.Log.ErrorProcessingStartScript" ), es );
}
// Now Compile our Script
// alternatively you could also support non compilable JSR223
// languages, see how we were doing before:
// http://github.com/rvalyi/jripple/blob/e6190fd89014a49b0faffae68c75762be124d899/
// src/org/pentaho/di/trans/steps/scriptvalues_mod/ScriptValuesMod.java
data.script = ( (Compilable) data.cx ).compile( strTransformScript );
} catch ( Exception e ) {
throw new KettleValueException( BaseMessages.getString( PKG, "Script.Log.CouldNotCompileJavascript" ), e );
}
}
// Filling the defined TranVars with the Values from the Row
//
Object[] outputRow = RowDataUtil.resizeArray( row, data.outputRowMeta.size() );
// Keep an index...
int outputIndex = rowMeta.size();
try {
try {
data.scope.put( "row", row );
for ( int i = 0; i < data.fields_used.length; i++ ) {
ValueMetaInterface valueMeta = rowMeta.getValueMeta( data.fields_used[ i ] );
Object valueData = row[ data.fields_used[ i ] ];
Object normalStorageValueData = valueMeta.convertToNormalStorageType( valueData );
data.scope.put( valueMeta.getName(), normalStorageValueData );
}
// also add the meta information for the hole row
//
data.scope.put( "rowMeta", rowMeta );
} catch ( Exception e ) {
throw new KettleValueException( BaseMessages.getString( PKG, "Script.Log.UnexpectedeError" ), e );
}
data.script.eval( data.scope );
if ( bFirstRun ) {
bFirstRun = false;
// Check if we had a Transformation Status
Object tran_stat = data.scope.get( "trans_Status" );
if ( tran_stat != null ) { // TODO AKRETION not sure: !=
// ScriptableObject.NOT_FOUND
bWithTransStat = true;
if ( log.isDetailed() ) {
logDetailed( ( "tran_Status found. Checking transformation status while script execution." ) );
}
} else {
if ( log.isDetailed() ) {
logDetailed( ( "No tran_Status found. Transformation status checking not available." ) );
}
bWithTransStat = false;
}
}
if ( bWithTransStat ) {
iTranStat = (Integer) data.scope.get( "trans_Status" ); // TODO
// ARETION
// not
// sure
// the
// casting
// is
// correct
} else {
iTranStat = CONTINUE_TRANSFORMATION;
}
if ( iTranStat == CONTINUE_TRANSFORMATION ) {
bRC = true;
for ( int i = 0; i < meta.getFieldname().length; i++ ) {
Object result = data.scope.get( meta.getFieldname()[ i ] );
Object valueData = getValueFromJScript( result, i );
if ( data.replaceIndex[ i ] < 0 ) {
outputRow[ outputIndex++ ] = valueData;
} else {
outputRow[ data.replaceIndex[ i ] ] = valueData;
}
}
putRow( data.outputRowMeta, outputRow );
} else {
switch ( iTranStat ) {
case SKIP_TRANSFORMATION:
// eat this row.
bRC = true;
break;
case ABORT_TRANSFORMATION:
if ( data.cx != null ) {
// Context.exit(); TODO AKRETION not sure
stopAll();
}
setOutputDone();
bRC = false;
break;
case ERROR_TRANSFORMATION:
if ( data.cx != null ) {
// Context.exit(); TODO AKRETION not sure
setErrors( 1 );
}
stopAll();
bRC = false;
break;
default:
break;
}
// TODO: kick this "ERROR handling" junk out now that we have
// solid error handling in place.
//
}
} catch ( ScriptException e ) {
throw new KettleValueException( BaseMessages.getString( PKG, "Script.Log.JavascriptError" ), e );
}
return bRC;
}
public Object getValueFromJScript( Object result, int i ) throws KettleValueException {
String fieldName = meta.getFieldname()[ i ];
if ( !Utils.isEmpty( fieldName ) ) {
// res.setName(meta.getRename()[i]);
// res.setType(meta.getType()[i]);
try {
return ( result == null ) ? null
: JavaScriptUtils.convertFromJs( result, meta.getType()[ i ], fieldName );
} catch ( Exception e ) {
throw new KettleValueException( BaseMessages.getString( PKG, "Script.Log.JavascriptError" ), e );
}
} else {
throw new KettleValueException( "No name was specified for result value #" + ( i + 1 ) );
}
}
public RowMetaInterface getOutputRowMeta() {
return data.outputRowMeta;
}
public boolean processRow( StepMetaInterface smi, StepDataInterface sdi ) throws KettleException {
meta = (ScriptMeta) smi;
data = (ScriptData) sdi;
Object[] r = getRow(); // Get row from input rowset & set row busy!
if ( r == null ) {
// Modification for Additional End Function
try {
if ( data.cx != null ) {
// Checking for EndScript
if ( strEndScript != null && strEndScript.length() > 0 ) {
// Script endScript =
// data.cx.compileString(strEndScript, "trans_End", 1,
// null);
// endScript.exec(data.cx, data.scope);
data.cx.eval( strEndScript, data.scope );
if ( log.isDetailed() ) {
logDetailed( ( "End Script found!" ) );
}
} else {
if ( log.isDetailed() ) {
logDetailed( ( "No end Script found!" ) );
}
}
}
} catch ( Exception e ) {
logError( BaseMessages.getString( PKG, "Script.Log.UnexpectedeError" ) + " : " + e.toString() );
logError( BaseMessages.getString( PKG, "Script.Log.ErrorStackTrace" )
+ Const.CR + Const.getStackTracker( e ) );
setErrors( 1 );
stopAll();
}
setOutputDone();
return false;
}
// Getting the Row, with the Transformation Status
try {
addValues( getInputRowMeta(), r );
} catch ( KettleValueException e ) {
String location = null;
if ( e.getCause() instanceof ScriptException ) {
ScriptException ee = (ScriptException) e.getCause();
location = "--> " + ee.getLineNumber() + ":" + ee.getColumnNumber();
//
}
if ( getStepMeta().isDoingErrorHandling() ) {
putError( getInputRowMeta(), r, 1, e.getMessage() + Const.CR + location, null, "SCR-001" );
bRC = true; // continue by all means, even on the first row and
// out of this ugly design
} else {
throw ( e );
}
}
if ( checkFeedback( getLinesRead() ) ) {
logBasic( BaseMessages.getString( PKG, "Script.Log.LineNumber" ) + getLinesRead() );
}
return bRC;
}
public boolean init( StepMetaInterface smi, StepDataInterface sdi ) {
meta = (ScriptMeta) smi;
data = (ScriptData) sdi;
if ( super.init( smi, sdi ) ) {
// Add init code here.
// Get the actual Scripts from our MetaData
jsScripts = meta.getJSScripts();
for ( int j = 0; j < jsScripts.length; j++ ) {
switch ( jsScripts[ j ].getScriptType() ) {
case ScriptValuesScript.TRANSFORM_SCRIPT:
strTransformScript = jsScripts[ j ].getScript();
break;
case ScriptValuesScript.START_SCRIPT:
strStartScript = jsScripts[ j ].getScript();
break;
case ScriptValuesScript.END_SCRIPT:
strEndScript = jsScripts[ j ].getScript();
break;
default:
break;
}
}
return true;
}
return false;
}
public void dispose( StepMetaInterface smi, StepDataInterface sdi ) {
try {
if ( data.cx != null ) {
return;
// Context.exit(); TODO AKRETION not sure
}
} catch ( Exception er ) {
// Eat this error, it's typically :
// "Calling Context.exit without previous Context.enter"
// logError(BaseMessages.getString(PKG, "System.Log.UnexpectedError"), er);
}
super.dispose( smi, sdi );
}
}