kettle插件开发

场景

公司业务需要进行kettle插件开发,领导让做一个身份证验证的demo,验证后输出数据新增加一个标志字段(该字段可自定义)。效果如下:


kettle插件开发_第1张图片
插件弹窗

kettle插件开发_第2张图片
运行日志

本人的开发环境

开发工具: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项目

kettle插件开发_第3张图片
工程构建

在项目根目录下创建core、dbdialog、engine、plugins、ui四个目录:


kettle插件开发_第4张图片
项目结构

在源码解压目录找到对应目录,将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
目录结构截图:


kettle插件开发_第5张图片
目录结构

找到安装解压目录,注意,这里是安装文件的解压目录:


kettle插件开发_第6张图片
导入目录

复制上面4个目录到项目根目录下,同时,进入lib目录下,删除kettle开头的三个jar包:
kettle插件开发_第7张图片
删除的jar包

目录结构截图:
kettle插件开发_第8张图片
目录结构

项目添加刚复制过来的lib:
选中项目->Build Path -> Add Libraries -> User Libraries -> New ,新建library,,点击Add JARS添加当前目录下jar,添加lib以及libswt下的swt.jar,swt.jar根据自己当前的操作系统选择对应版本。如我的机器是win64


kettle插件开发_第9张图片
build path

将core、dbdialog、engine、ui四个目录,作为源码目录:
选中文件夹,右键,Build Path -> Use as Source Folder
kettle插件开发_第10张图片
源码目录

最后形成项目结构:
kettle插件开发_第11张图片
最后目录结构

启动ui包下的org.pentaho.di.ui.spoon.Spoon.java(run as java application)能正确打开,则为正确。
kettle插件开发_第12张图片
启动

模板插件项目搭建

Eclipse中导入提供的模板插件工程

kettle插件开发_第13张图片
模板工程

将插件项目中的src目录链接到上面的kettle源码运行工程中
选中上面的源码运行工程,项目右键——》Build Path ——》Link Source
kettle插件开发_第14张图片
link source

然后在上面源码工程的plugins目录中新建steps目录,然后在steps目录下新建TemplateStep目录,然后将该标准模板工程下的distrib文件夹下的icon.png和plugin.xml文件拷入上面源码工程的刚才新建的TemplateStep文件夹下。( 暂时这样,后续如果要打包就不用复制了,使用注解的方法配置)
kettle插件开发_第15张图片
复制文件到源码工程

TemplateStep .jar包不要拷,因为有时候 TemplateStep中的代码变了但是没有重新打包成TemplateStep .jar并且拷入kettle的 TemplateStep文件夹下,那么插件的改变依然不会出现,因为kettle会依旧采用以前的jar。
重新启动sqoon 会发现在转换中出现了一demon下面有一个按钮,点击就可以进行插件开发了
kettle插件开发_第16张图片
导入插件后的样式

比如我们在上面TemplateStep工程包下的 dialog类中找到open()方法加入一条 语句
System.out.println(“hello kettle!”);
kettle插件开发_第17张图片
调试开发

然后重启sqoon ,再点击demon下的 按钮,在控制台会看见 hello kettle! (注意sqoon以debug方式启动)
kettle插件开发_第18张图片
运行结果

至此kettle开发环境搭建成功。
时间有限,也可以使用我搭建好的两个项目,导入项目到eclipse后link source插件项目到源码工程即可
源码运行项目下载: 百度云盘下载 提取码:l3a8
插件项目下载: 百度云盘下载 提取码:2131

将插件项目中的src目录链接到上面的kettle源码运行工程中
选中上面的源码运行工程,项目右键——》Build Path ——》Link Source

kettle插件开发_第19张图片
link source

界面开发

大牛可以从头到尾全部自己手写代码编写页面,本人能力有限只能"参考"其他插件的界面进行更改。"参考"过程就是安装完上面提供的kettlep di-ce-7.1.0.0-12软件,打开Spoon。挨个查看插件界面,找到自己理想界面的类似插件。如下,本人在转换下设置字段值插件找到理想界面。


kettle插件开发_第20张图片
设置字段值

kettle插件开发_第21张图片
设置字段值界面

接下来找到该插件界面代码。
内置插件的界面代码,都在源码工程的UI目录下的org.pentaho.di.ui.trans.steps包下。根据步骤名称,找到对应的界面StepDialogInterface实现类。跟踪代码也就找到了StepMetaInterface实现类;StepInterface实现类;StepDataInterface实现类。
这几个类的作用:


kettle插件开发_第22张图片
类的作用
kettle插件开发_第23张图片
ui目录

dialog实现类
实现类

将这几个类的核心代码复制到上面插件项目中对应的类中,进行修改。
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 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插件开发_第24张图片
打包

在kettle安装目录的plugins目录新建steps目录
kettle插件开发_第25张图片
steps目录

新建一个该插件文件夹
kettle插件开发_第26张图片
插件文件夹

将刚才打包好的jar包放置到该文件夹下,重启Spoon即可使用。
kettle插件开发_第27张图片
jar包放置

重启后kettle截图:
kettle插件开发_第28张图片
插件

此方式只适合使用@Step注解配置插件信息的方式(参考上面meta类源码),还有一种xml配置方式不推荐

使用api调用自定义的插件注意事项

需要设置插件加载位置,如在项目的listener中设置该参数:

设置插件的加载位置
System.setProperty("KETTLE_PLUGIN_BASE_FOLDERS", System.getProperty("catalina.home")+"/plugins");

然后将打包好的插件放到该目录下(可以参考我的目录结构),即可通过api调用。


插件目录

至此插件开发完成。

其他小tip

kettle6中创建job,使用资源库加载转换时,默认路径为该转换的全路径


kettle插件开发_第29张图片
1

而kettle6以上版本转换路径如果和job路径相同,则会使用:${Internal.Entry.Current.Directory}代替相同路径。


kettle插件开发_第30张图片
2

参考文献:
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

你可能感兴趣的:(kettle插件开发)