场景
公司业务需要进行kettle插件开发,领导让做一个身份证验证的demo,验证后输出数据新增加一个标志字段(该字段可自定义)。效果如下:
本人的开发环境
开发工具:eclipse-Oxygen
JDK:1.8
Kettle源码包版本:pentaho-kettle-7.1。百度云盘下载 提取码:wwnv
Kettle版本:pdi-ce-7.1.0.0-12。百度云盘下载 提取码:1sw8
项目中使用的模板插件TemplateStepPlugin。百度云盘下载 提取码:5h8f
注:嫌麻烦可直接略过一下环境搭建,从下面下载我搭建好的环境,导入到eclipse即可
eclipse中搭建源码运行环境
创建普通java项目
在项目根目录下创建core、dbdialog、engine、plugins、ui四个目录:
在源码解压目录找到对应目录,将java等相关文件复制到刚创建的四个目录下:
注意直接复制src下文件即可,不包含src目录本身
pentaho-kettle-7.1_bak\core\src\ -> core
pentaho-kettle-7.1_bak\dbdialog\src\ -> dbdialog
pentaho-kettle-7.1_bak\engine\src\ -> engine
pentaho-kettle-7.1_bak\plugins\ -> plugins
pentaho-kettle-7.1_bak\ui\src;pentaho-kettle-7.1_bak\assembly\package-res\ui\ -> ui
再添加一个images目录,为了方便直接使用自带的svg图片:
pentaho-kettle-7.1\ui\ui\images –> ui\images
目录结构截图:
找到安装解压目录,注意,这里是安装文件的解压目录:
复制上面4个目录到项目根目录下,同时,进入lib目录下,删除kettle开头的三个jar包:
目录结构截图:
项目添加刚复制过来的lib:
选中项目->Build Path -> Add Libraries -> User Libraries -> New ,新建library,,点击Add JARS添加当前目录下jar,添加lib以及libswt下的swt.jar,swt.jar根据自己当前的操作系统选择对应版本。如我的机器是win64
将core、dbdialog、engine、ui四个目录,作为源码目录:
选中文件夹,右键,Build Path -> Use as Source Folder
最后形成项目结构:
启动ui包下的org.pentaho.di.ui.spoon.Spoon.java(run as java application)能正确打开,则为正确。
模板插件项目搭建
Eclipse中导入提供的模板插件工程
将插件项目中的src目录链接到上面的kettle源码运行工程中
选中上面的源码运行工程,项目右键——》Build Path ——》Link Source
然后在上面源码工程的plugins目录中新建steps目录,然后在steps目录下新建TemplateStep目录,然后将该标准模板工程下的distrib文件夹下的icon.png和plugin.xml文件拷入上面源码工程的刚才新建的TemplateStep文件夹下。( 暂时这样,后续如果要打包就不用复制了,使用注解的方法配置)
TemplateStep .jar包不要拷,因为有时候 TemplateStep中的代码变了但是没有重新打包成TemplateStep .jar并且拷入kettle的 TemplateStep文件夹下,那么插件的改变依然不会出现,因为kettle会依旧采用以前的jar。
重新启动sqoon 会发现在转换中出现了一demon下面有一个按钮,点击就可以进行插件开发了
比如我们在上面TemplateStep工程包下的 dialog类中找到open()方法加入一条 语句
System.out.println(“hello kettle!”);
然后重启sqoon ,再点击demon下的 按钮,在控制台会看见 hello kettle! (注意sqoon以debug方式启动)
至此kettle开发环境搭建成功。
时间有限,也可以使用我搭建好的两个项目,导入项目到eclipse后link source插件项目到源码工程即可
源码运行项目下载: 百度云盘下载 提取码:l3a8
插件项目下载: 百度云盘下载 提取码:2131
将插件项目中的src目录链接到上面的kettle源码运行工程中
选中上面的源码运行工程,项目右键——》Build Path ——》Link Source
界面开发
大牛可以从头到尾全部自己手写代码编写页面,本人能力有限只能"参考"其他插件的界面进行更改。"参考"过程就是安装完上面提供的kettlep di-ce-7.1.0.0-12软件,打开Spoon。挨个查看插件界面,找到自己理想界面的类似插件。如下,本人在转换下设置字段值插件找到理想界面。
接下来找到该插件界面代码。
内置插件的界面代码,都在源码工程的UI目录下的org.pentaho.di.ui.trans.steps包下。根据步骤名称,找到对应的界面StepDialogInterface实现类。跟踪代码也就找到了StepMetaInterface实现类;StepInterface实现类;StepDataInterface实现类。
这几个类的作用:
将这几个类的核心代码复制到上面插件项目中对应的类中,进行修改。
dialog类源码,篇幅过长,可跳过自行调试。
package plugin.template;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.pentaho.di.core.Const;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.row.RowMetaInterface;
import org.pentaho.di.core.util.Utils;
import org.pentaho.di.i18n.BaseMessages;
import org.pentaho.di.trans.TransMeta;
import org.pentaho.di.trans.step.BaseStepMeta;
import org.pentaho.di.trans.step.StepDialogInterface;
import org.pentaho.di.trans.step.StepMeta;
import org.pentaho.di.ui.core.dialog.ErrorDialog;
import org.pentaho.di.ui.core.widget.ColumnInfo;
import org.pentaho.di.ui.core.widget.TableView;
import org.pentaho.di.ui.trans.step.BaseStepDialog;
/**
* 该类的主要作用就是,设置插件弹窗样式。及将用户设置写入到StepMetaInterface的实现类
* 弹窗开发,参考swt开发
*/
public class TemplateStepDialog extends BaseStepDialog implements StepDialogInterface {
private static Class> PKG = TemplateStepMeta.class;
private Label wlStepname;
private Text wStepname;
private FormData fdlStepname, fdStepname;
private Label wlFields;
private TableView wFields;
private FormData fdlFields, fdFields;
//最好将一行的组件集中在一起定义,识别性高
private Label flagLabel;//标签组件
private Text flagText;//输入框组件
private FormData fdFlagLabel, fdFlagText;//用于给组件定位
private TemplateStepMeta input;
private Map inputFields;
private ColumnInfo[] colinf;
//shell相当于一个活动的窗口,in为StepMetaInterface实现类,transMeta转换的元信息
public TemplateStepDialog(Shell parent, Object in, TransMeta transMeta, String sname) {
super(parent, (BaseStepMeta) in, transMeta, sname);
input = (TemplateStepMeta) in;
inputFields = new HashMap();
}
//kettle加载插件后,双击插件会调用open方法,进行弹窗
public String open() {
Shell parent = getParent();
Display display = parent.getDisplay();
shell = new Shell( parent, SWT.DIALOG_TRIM | SWT.RESIZE | SWT.MAX | SWT.MIN );
props.setLook( shell );
setShellImage( shell, input );
ModifyListener lsMod = new ModifyListener() {
public void modifyText( ModifyEvent e ) {
input.setChanged();
}
};
changed = input.hasChanged();
FormLayout formLayout = new FormLayout();
formLayout.marginWidth = Const.FORM_MARGIN;
formLayout.marginHeight = Const.FORM_MARGIN;
shell.setLayout( formLayout );
shell.setText( BaseMessages.getString( PKG, "CheckValueFieldDialog.Shell.Label" ) );
int middle = props.getMiddlePct();
int margin = Const.MARGIN;
// Stepname line
wlStepname = new Label( shell, SWT.RIGHT );
wlStepname.setText( BaseMessages.getString( PKG, "CheckValueFieldDialog.Stepname.Label" ) );
props.setLook( wlStepname );
fdlStepname = new FormData();
fdlStepname.left = new FormAttachment( 0, 0 );
fdlStepname.right = new FormAttachment( middle, -margin );
fdlStepname.top = new FormAttachment( 0, margin );
wlStepname.setLayoutData( fdlStepname );
wStepname = new Text( shell, SWT.SINGLE | SWT.LEFT | SWT.BORDER );
wStepname.setText( stepname );
props.setLook( wStepname );
wStepname.addModifyListener( lsMod );
fdStepname = new FormData();
fdStepname.left = new FormAttachment( middle, 0 );
fdStepname.top = new FormAttachment( 0, margin );
fdStepname.right = new FormAttachment( 100, 0 );
wStepname.setLayoutData( fdStepname );
//添加一行设置标志字段的名称
flagLabel = new Label( shell, SWT.RIGHT );//新建一个label
flagLabel.setText( BaseMessages.getString( PKG, "CheckValueFieldDialog.Flag.Label" ) );//设置label的显示文字,plugin.template.messages中配置
props.setLook( flagLabel );//新建组建后调用此方法
fdFlagLabel = new FormData();//新建定位的类
fdFlagLabel.left = new FormAttachment( 0, 0 );//距离左边边距0px,偏移0.参数作用参考swt编程
fdFlagLabel.right = new FormAttachment( middle, -margin );
fdFlagLabel.top = new FormAttachment( wStepname, margin );//第一个参数为该组件的上一个组件名称
flagLabel.setLayoutData( fdFlagLabel );//为label绑定定位
flagText = new Text( shell, SWT.SINGLE | SWT.LEFT | SWT.BORDER );//新建一个标示字段输入框
flagText.setText(input.getFlagField());//meta类中我初始化为flag
props.setLook( flagText );
flagText.addModifyListener( lsMod );
fdFlagText = new FormData();
fdFlagText.left = new FormAttachment( middle, 0 );
fdFlagText.top = new FormAttachment( wStepname, margin );
fdFlagText.right = new FormAttachment( 100, 0 );
flagText.setLayoutData( fdFlagText );
wlFields = new Label( shell, SWT.NONE );
wlFields.setText(BaseMessages.getString( PKG, "CheckValueFieldDialog.Fields.Label" ) );
props.setLook( wlFields );
fdlFields = new FormData();
fdlFields.left = new FormAttachment( 0, 0 );
fdlFields.top = new FormAttachment( flagText, margin );
wlFields.setLayoutData( fdlFields );
final int FieldsCols = 2;
final int FieldsRows = input.getFieldName().length;
colinf = new ColumnInfo[FieldsCols];
colinf[0] =
new ColumnInfo(
BaseMessages.getString( PKG, "CheckValueFieldDialog.ColumnInfo.Name" ), ColumnInfo.COLUMN_TYPE_CCOMBO,
new String[] { "" }, false );
colinf[1] =
new ColumnInfo(
BaseMessages.getString( PKG, "CheckValueFieldDialog.ColumnInfo.ValueFromField" ),
ColumnInfo.COLUMN_TYPE_CCOMBO, new String[] { "" }, false );
wFields =
new TableView(
transMeta, shell, SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI, colinf, FieldsRows, lsMod, props );
fdFields = new FormData();
fdFields.left = new FormAttachment( 0, 0 );
fdFields.top = new FormAttachment( wlFields, margin );
fdFields.right = new FormAttachment( 100, 0 );
fdFields.bottom = new FormAttachment( 100, -50 );
wFields.setLayoutData( fdFields );
//
// Search the fields in the background
final Runnable runnable = new Runnable() {
public void run() {
StepMeta stepMeta = transMeta.findStep( stepname );
if ( stepMeta != null ) {
try {
RowMetaInterface row = transMeta.getPrevStepFields( stepMeta );
// Remember these fields...
for ( int i = 0; i < row.size(); i++ ) {
inputFields.put( row.getValueMeta( i ).getName(), Integer.valueOf( i ) );
}
setComboBoxes();//设置表格中的下拉框
} catch ( KettleException e ) {
logError( "It was not possible to get the list of input fields from previous steps", e );
}
}
}
};
new Thread( runnable ).start();
// Some buttons
wOK = new Button( shell, SWT.PUSH );
wOK.setText( BaseMessages.getString( PKG, "System.Button.OK" ) );
wGet = new Button( shell, SWT.PUSH );
wGet.setText( BaseMessages.getString( PKG, "System.Button.GetFields" ) );
wCancel = new Button( shell, SWT.PUSH );
wCancel.setText( BaseMessages.getString( PKG, "System.Button.Cancel" ) );
setButtonPositions( new Button[] { wOK, wGet, wCancel }, margin, wFields );
// Add listeners
lsCancel = new Listener() {
public void handleEvent( Event e ) {
cancel();
}
};
lsGet = new Listener() {
public void handleEvent( Event e ) {
get();
}
};
lsOK = new Listener() {
public void handleEvent( Event e ) {
ok();
}
};
wCancel.addListener( SWT.Selection, lsCancel );
wGet.addListener( SWT.Selection, lsGet );
wOK.addListener( SWT.Selection, lsOK );
lsDef = new SelectionAdapter() {
public void widgetDefaultSelected( SelectionEvent e ) {
ok();
}
};
wStepname.addSelectionListener( lsDef );
// Detect X or ALT-F4 or something that kills this window...
shell.addShellListener( new ShellAdapter() {
public void shellClosed( ShellEvent e ) {
cancel();
}
} );
// Set the shell size, based upon previous time...
setSize();
shell.setSize(350, 300);//设置窗口的大小
getData();
input.setChanged( changed );
shell.open();
while ( !shell.isDisposed() ) {
if ( !display.readAndDispatch() ) {
display.sleep();
}
}
return stepname;
}
//设置表格单元格后的下拉框
protected void setComboBoxes() {
// Something was changed in the row.
//
final Map fields = new HashMap();
// Add the currentMeta fields...
fields.putAll( inputFields );
Set keySet = fields.keySet();
List entries = new ArrayList( keySet );
String[] fieldNames = entries.toArray( new String[entries.size()] );
String[] checkTypes = new String[]{"身份证格式校验","民族校验"};
Const.sortStrings( fieldNames );
colinf[0].setComboValues( fieldNames );
colinf[1].setComboValues( checkTypes );
}
/**
* Copy information from the meta-data input to the dialog fields.
*/
public void getData() {
wStepname.setText( stepname );
for ( int i = 0; i < input.getFieldName().length; i++ ) {
TableItem item = wFields.table.getItem( i );
String name = input.getFieldName()[i];
String type = input.getCheckValue()[i];
if ( name != null ) {
item.setText( 1, name );
}
if ( type != null ) {
item.setText( 2, type );
}
}
wFields.setRowNums();
wFields.optWidth( true );
wStepname.selectAll();
wStepname.setFocus();
}
private void cancel() {
stepname = null;
input.setChanged( changed );
dispose();
}
private void ok() {
if (wStepname.getText() == null || "".equals(wStepname.getText().trim() )) {
return;
}
stepname = wStepname.getText(); // return value
String flagField = flagText.getText();
//将用户输入的标志字段赋值给meta类,供step类使用
input.setFlagField(flagField);
int count = wFields.nrNonEmpty();
input.allocate( count );
//CHECKSTYLE:Indentation:OFF
for ( int i = 0; i < count; i++ ) {
TableItem item = wFields.getNonEmpty( i );
input.getFieldName()[i] = item.getText( 1 );
input.getCheckValue()[i] = item.getText( 2 );
}
dispose();
}
private void get() {
try {
RowMetaInterface r = transMeta.getPrevStepFields( stepMeta );
if ( r != null ) {
BaseStepDialog.getFieldsFromPrevious( r, wFields, 1, new int[] { 1 }, null, -1, -1, null );
}
} catch ( KettleException ke ) {
new ErrorDialog( shell, BaseMessages.getString( PKG, "System.Dialog.GetFieldsFailed.Title" ), BaseMessages
.getString( PKG, "System.Dialog.GetFieldsFailed.Message" ), ke );
}
}
}
StepMetaInterface实现类源码:
package plugin.template;
import java.util.List;
import org.eclipse.swt.widgets.Shell;
import org.pentaho.di.core.CheckResult;
import org.pentaho.di.core.CheckResultInterface;
import org.pentaho.di.core.Const;
import org.pentaho.di.core.annotations.Step;
import org.pentaho.di.core.database.DatabaseMeta;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.exception.KettleXMLException;
import org.pentaho.di.core.row.RowMetaInterface;
import org.pentaho.di.core.row.ValueMeta;
import org.pentaho.di.core.row.ValueMetaInterface;
import org.pentaho.di.core.util.Utils;
import org.pentaho.di.core.variables.VariableSpace;
import org.pentaho.di.core.xml.XMLHandler;
import org.pentaho.di.i18n.BaseMessages;
import org.pentaho.di.repository.ObjectId;
import org.pentaho.di.repository.Repository;
import org.pentaho.di.shared.SharedObjectInterface;
import org.pentaho.di.trans.Trans;
import org.pentaho.di.trans.TransMeta;
import org.pentaho.di.trans.step.BaseStepMeta;
import org.pentaho.di.trans.step.StepDataInterface;
import org.pentaho.di.trans.step.StepDialogInterface;
import org.pentaho.di.trans.step.StepInterface;
import org.pentaho.di.trans.step.StepMeta;
import org.pentaho.di.trans.step.StepMetaInterface;
import org.pentaho.metastore.api.IMetaStore;
import org.w3c.dom.Node;
/**
* 该类相当于一个数据承载传递类,有点像实体类的感觉
*
*/
//使用注解的方式代替plugin.xml配置文件
@Step(
id="TemplatePlugin",//插件ID必须唯一
image = "icon.png",//插件在kettle中显示图标,默认从src目录下找
name = "自定义数据校验",//插件在kettle中显示的名称
description="根据扩展规则验证数据",//插件子啊kettle中的描述
categoryDescription="Demon"//插件在kettle中的分类
)
public class TemplateStepMeta extends BaseStepMeta implements StepMetaInterface {
private static Class> PKG = TemplateStepMeta.class; // for i18n purposes
private String[] fieldName;
private String[] checkType;
private String flagField = "mt_flag";//用户设置的标志字段名称就保存在这里,添加set get方法
public String getFlagField() {
return flagField;
}
public void setFlagField(String flagField) {
this.flagField = flagField;
}
public TemplateStepMeta() {
super(); // allocate BaseStepMeta
}
/**
* @return Returns the fieldName.
*/
public String[] getFieldName() {
return fieldName;
}
/**
* @param fieldName
* The fieldName to set.
*/
public void setFieldName( String[] fieldName ) {
this.fieldName = fieldName;
}
/**
* @return Returns the checkType.
*/
public String[] getCheckValue() {
return checkType;
}
/**
* @param checkType
* The checkType to set.
*/
public void setCheckValue( String[] checkType ) {
this.checkType = checkType;
}
public void loadXML( Node stepnode, List databases, IMetaStore metaStore ) throws KettleXMLException {
readData( stepnode, databases );
}
public void allocate( int count ) {
fieldName = new String[count];
checkType = new String[count];
}
public Object clone() {
TemplateStepMeta retval = (TemplateStepMeta) super.clone();
int count = fieldName.length;
retval.allocate( count );
System.arraycopy( fieldName, 0, retval.fieldName, 0, count );
System.arraycopy( checkType, 0, retval.checkType, 0, count );
return retval;
}
//如果想在下一个步骤获取这个步骤新增的字段名称,必须在这里新增字段
public void getFields(RowMetaInterface r, String origin, RowMetaInterface[] info, StepMeta nextStep, VariableSpace space) {
//给输入数据中新增字段
ValueMetaInterface v = new ValueMeta();
v.setName(flagField);//设置字段的名称
v.setType(ValueMeta.TYPE_BOOLEAN);
v.setTrimType(ValueMeta.TRIM_TYPE_BOTH);
v.setOrigin(origin);
r.addValueMeta(v);
}
private void readData( Node stepnode, List extends SharedObjectInterface> databases ) throws KettleXMLException {
try {
Node fields = XMLHandler.getSubNode( stepnode, "fields" );
int count = XMLHandler.countNodes( fields, "field" );
allocate( count );
for ( int i = 0; i < count; i++ ) {
Node fnode = XMLHandler.getSubNodeByNr( fields, "field", i );
fieldName[i] = XMLHandler.getTagValue( fnode, "name" );
checkType[i] = XMLHandler.getTagValue( fnode, "replaceby" );
}
} catch ( Exception e ) {
throw new KettleXMLException( BaseMessages.getString(
PKG, "TemplateStepMeta.Exception.UnableToReadStepInfoFromXML" ), e );
}
}
public void setDefault() {
int count = 0;
allocate( count );
for ( int i = 0; i < count; i++ ) {
fieldName[i] = "field" + i;
checkType[i] = "";
}
}
public String getXML() {
StringBuilder retval = new StringBuilder();
retval.append( " " + Const.CR );
for ( int i = 0; i < fieldName.length; i++ ) {
retval.append( " " + Const.CR );
retval.append( " " + XMLHandler.addTagValue( "name", fieldName[i] ) );
retval.append( " " + XMLHandler.addTagValue( "replaceby", checkType[i] ) );
retval.append( " " + Const.CR );
}
retval.append( " " + Const.CR );
return retval.toString();
}
//从资源库读取用户设置的步骤信息
public void readRep( Repository rep, IMetaStore metaStore, ObjectId id_step, List databases ) throws KettleException {
try {
int nrfields = rep.countNrStepAttributes( id_step, "field_name" );
allocate( nrfields );
flagField = rep.getStepAttributeString(id_step, "flag_field");//获取用户设置的标志字段名称
for ( int i = 0; i < nrfields; i++ ) {
fieldName[i] = rep.getStepAttributeString( id_step, i, "field_name" );
checkType[i] = rep.getStepAttributeString( id_step, i, "check_type" );
}
} catch ( Exception e ) {
throw new KettleException( BaseMessages.getString(
PKG, "TemplateStepMeta.Exception.UnexpectedErrorReadingStepInfoFromRepository" ), e );
}
}
//保存用户设置的步骤信息到资源库
public void saveRep( Repository rep, IMetaStore metaStore, ObjectId id_transformation, ObjectId id_step ) throws KettleException {
try {
rep.saveStepAttribute(id_transformation, id_step, "flag_field", flagField); //保存标志字段
for ( int i = 0; i < fieldName.length; i++ ) {
rep.saveStepAttribute( id_transformation, id_step, i, "field_name", fieldName[i] );
rep.saveStepAttribute( id_transformation, id_step, i, "check_type", checkType[i] );
}
} catch ( Exception e ) {
throw new KettleException( BaseMessages.getString(
PKG, "TemplateStepMeta.Exception.UnableToSaveStepInfoToRepository" )
+ id_step, e );
}
}
public void check( List remarks, TransMeta transMeta, StepMeta stepMeta,
RowMetaInterface prev, String[] input, String[] output, RowMetaInterface info, VariableSpace space,
Repository repository, IMetaStore metaStore ) {
}
public StepDialogInterface getDialog(Shell shell, StepMetaInterface meta, TransMeta transMeta, String name) {
return new TemplateStepDialog(shell, meta, transMeta, name);
}
public StepInterface getStep(StepMeta stepMeta, StepDataInterface stepDataInterface, int cnr, TransMeta transMeta, Trans disp) {
return new TemplateStep(stepMeta, stepDataInterface, cnr, transMeta, disp);
}
public StepDataInterface getStepData() {
return new TemplateStepData();
}
public boolean supportsErrorHandling() {
return true;
}
}
StepInterface实现类源码:
package plugin.template;
import java.util.Arrays;
import org.pentaho.di.core.Const;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.row.RowDataUtil;
import org.pentaho.di.core.row.RowMetaInterface;
import org.pentaho.di.core.row.ValueMeta;
import org.pentaho.di.core.row.ValueMetaInterface;
import org.pentaho.di.trans.Trans;
import org.pentaho.di.trans.TransMeta;
import org.pentaho.di.trans.step.*;
import util.CheckUtil;
/**
* 该类为插件真正处理数据的类,每次数据来都会调用processRow方法
*
*/
public class TemplateStep extends BaseStep implements StepInterface {
private TemplateStepData data;
private TemplateStepMeta meta;
public TemplateStep(StepMeta s, StepDataInterface stepDataInterface, int c, TransMeta t, Trans dis) {
super(s, stepDataInterface, c, t, dis);
}
public boolean processRow(StepMetaInterface smi, StepDataInterface sdi) throws KettleException {
Object[] outputRow = null;
//校验合格为true,不合格为false
Boolean flag = true;
meta = (TemplateStepMeta) smi;
data = (TemplateStepData) sdi;
Object[] r = getRow(); //获取一行数据
if (r == null) //如果为空,则代表获取到最后一行数据,调用setOutputDone()方法
{
setOutputDone();
return false;
}
if (first) {
first = false;
//如果是第一行则保存数据行元信息到data类中,后续使用
data.outputRowMeta = (RowMetaInterface) getInputRowMeta().clone();
//为数据行新增加一个字段(就是标志字段)
meta.getFields(data.outputRowMeta, getStepname(), null, null, this);
logBasic("template step initialized successfully");
}
String[] checkValue = meta.getCheckValue();
String[] fieldName = meta.getFieldName();
//这里为业务逻辑代码,如校验身份证等
for (int i = 0; i < checkValue.length; i++) {
//验证身份证
if("身份证格式校验".equals(checkValue[i])){
//获取身份证校验字段的索引
int index = data.outputRowMeta.indexOfValue(fieldName[i]);
//获取校验字段的值
String sfzh = data.outputRowMeta.getString(r, index);
boolean rs = CheckUtil.isIDNumber(sfzh);
if(!rs){
flag = false;
}
}else if("民族校验".equals(checkValue[i])) {//验证民族代码,暂停
}
}
//此方法为刚才新增字段赋值
outputRow = RowDataUtil.addValueData(r, data.outputRowMeta.size() - 1, flag);
//将结果写入下一个步骤
putRow(data.outputRowMeta, outputRow);
if (checkFeedback(getLinesRead())) {
logBasic("Linenr " + getLinesRead()); // Some basic logging
}
return true;
}
public boolean init(StepMetaInterface smi, StepDataInterface sdi) {
meta = (TemplateStepMeta) smi;
data = (TemplateStepData) sdi;
return super.init(smi, sdi);
}
public void dispose(StepMetaInterface smi, StepDataInterface sdi) {
meta = (TemplateStepMeta) smi;
data = (TemplateStepData) sdi;
super.dispose(smi, sdi);
}
public void run() {
logBasic("Starting to run...");
try {
while (processRow(meta, data) && !isStopped())
;
} catch (Exception e) {
logError("Unexpected error : " + e.toString());
logError(Const.getStackTracker(e));
setErrors(1);
stopAll();
} finally {
dispose(meta, data);
logBasic("Finished, processing " + getLinesRead() + " rows");
markStop();
}
}
}
至此代码编写完成。
将插件打包到kettle中正式使用
将插件项目打包成jar包
在kettle安装目录的plugins目录新建steps目录
新建一个该插件文件夹
将刚才打包好的jar包放置到该文件夹下,重启Spoon即可使用。
重启后kettle截图:
此方式只适合使用@Step注解配置插件信息的方式(参考上面meta类源码),还有一种xml配置方式不推荐
使用api调用自定义的插件注意事项
需要设置插件加载位置,如在项目的listener中设置该参数:
设置插件的加载位置
System.setProperty("KETTLE_PLUGIN_BASE_FOLDERS", System.getProperty("catalina.home")+"/plugins");
然后将打包好的插件放到该目录下(可以参考我的目录结构),即可通过api调用。
至此插件开发完成。
其他小tip
kettle6中创建job,使用资源库加载转换时,默认路径为该转换的全路径
而kettle6以上版本转换路径如果和job路径相同,则会使用:${Internal.Entry.Current.Directory}代替相同路径。
参考文献:
https://blog.csdn.net/mar_ljh/article/details/89333215
https://blog.csdn.net/yujin753/article/details/42527967?locationNum=14&fps=1
https://blog.csdn.net/bluebelfast/article/details/43192995
https://www.cnblogs.com/xing901022/p/4090560.html
https://help.pentaho.com/Documentation/8.1/Developer_Center/PDI/Extend/000#Sample_Step_Plugin
https://wiki.pentaho.com/display/EAI/Latest+Pentaho+Data+Integration+%28aka+Kettle%29+Documentation
https://javadoc.pentaho.com/kettle530/kettle-engine-5.3.0.0-javadoc/overview-summary.html