kettle4.1源码研究

来源:互联网 发布:广州婚纱摄影排名 知乎 编辑:程序博客网 时间:2024/05/20 10:13
来自 http://blog.csdn.net/qq250941970/article/details/7695897

目录(?)[-]

  1. 一获取并编译源码
    1. 获取源码
      1. Svn获取
      2. 网页获取
    2. 编译源码
      1. 将项目加载到eclipse
      2. 编译
      3. 用源码运行Spoon
  2. 二源码分析
    1. 修改kettle界面
      1. 修改初始化界面
      2. 修改spoon主界面标题
      3. 修改spoon界面主菜单栏
      4. 修改spoon菜单栏
      5. 修改转换和job插件列表及图标
      6. 主面板布局
    2. Kettle数据转换流程
      1. 表输入流程
      2. 插入更新流程
    3. 插件研究
      1. kettle转换步骤工作组件
      2. 书写你自己的pluginxml
      3. 插件主要包含类介绍
      4. 开发插件实例
      5. 调用use define java class 插件

Kettle源码分析

一.获取并编译源码

1.1.           获取源码

Svn获取

svn://source.pentaho.org/svnkettleroot/Kettle/tags

网页获取

官方网站:http://kettle.pentaho.com/

1.2.           编译源码

将项目加载到eclipse

kettle项目拷贝到eclipseworkspace目录下,在eclipse中新建java project,项目名称和你拷贝过来的kettle文件夹名称一致

 

项目导入到eclipse中会出现一个错误,如下图,将这个文件的源码全部注释掉

 

编译

打开build.xml, 在右边的。Outline 点击kettle->run  as ->ant build

 

 

第一次编译的时候需要从网上下载几个文件,放在C:\Documents and Settings\Administrator\.subfloor,网络不好的话下载会比较慢,也可以直接文件放在C:\Documents and Settings\Administrator\下。编译完成后将bin目录下的.bat文件拷贝到Kettle目录下点击Spoon.bat运行,运行成功代表编译已近通过

用源码运行Spoon

Kettle源码工程本身可能是在linux64位机器上调试的,swt配置是linux64的库,所有在运行源码前需要修改成win32swt,步骤如下:工程à属性àJava Build Pathàlibrariesàadd jars

然后将linux64SWT库删除

最后打开src-uiàorg.pentaho.di.ui.spoonàSpoon.java  Run As àjava application

二.源码分析

2.1.           修改kettle界面

修改初始化界面

打开package org.pentaho.di.ui.spoonSpoon.Java,找到main函数,该main函数为Spoon工具的入口,找到如下语句

 

Splash splash = new Splash(display);

         该语句为spoon初始化显示的界面,跳到定义Splash.java,下面函数

canvas.addPaintListener(new PaintListener() {

      publicvoid paintControl(PaintEvent e) {

        String versionText = BaseMessages.getString(PKG"SplashDialog.Version") + " " + Const.VERSION//$NON-NLS-1$ //$NON-NLS-2$

       

        StringBuilder sb = new StringBuilder();

        String line = null;

       

        try {

          BufferedReader reader = new BufferedReader(newInputStreamReader(Splash.class.getClassLoader().getResourceAsStream("org/pentaho/di/ui/core/dialog/license/license.txt")));//$NON-NLS-1$

         

          while((line = reader.readLine()) != null) {

            sb.append(line + System.getProperty("line.separator")); //$NON-NLS-1$

          }

        } catch (Exception ex) {

          sb.append(""); //$NON-NLS-1$

          Log.warn(BaseMessages.getString(PKG"SplashDialog.LicenseTextNotFound")); //$NON-NLS-1$

        }

       

        String licenseText = sb.toString();

        e.gc.drawImage(kettle_image, 0, 0);

 

        // If this is a Milestone or RC release, warn the user

        if (Const.RELEASE.equals(Const.ReleaseType.MILESTONE)) {

        versionText = BaseMessages.getString(PKG"SplashDialog.DeveloperRelease") + " - " + versionText; //$NON-NLS-1$ //$NON-NLS-2$

          drawVersionWarning(e);

        } elseif (Const.RELEASE.equals(Const.ReleaseType.RELEASE_CANDIDATE)) {

          versionText = BaseMessages.getString(PKG"SplashDialog.ReleaseCandidate") + " - " + versionText;  //$NON-NLS-1$//$NON-NLS-2$

        }

        elseif (Const.RELEASE.equals(Const.ReleaseType.PREVIEW)) {

          versionText = BaseMessages.getString(PKG"SplashDialog.PreviewRelease") + " - " + versionText;  //$NON-NLS-1$//$NON-NLS-2$

        }

        elseif (Const.RELEASE.equals(Const.ReleaseType.GA)) {

            versionText = BaseMessages.getString(PKG"SplashDialog.GA") + " - " + versionText;  //$NON-NLS-1$//$NON-NLS-2$

          }

 

        Font verFont = new Font(e.display"Helvetica", 11, SWT.BOLD); //$NON-NLS-1$

        e.gc.setFont(verFont);

        e.gc.drawText(versionText, 290, 205, true);

               

        // try using the desired font size for the license text

        int fontSize = 8;

        Font licFont = new Font(e.display"Helvetica", fontSize, SWT.NORMAL); //$NON-NLS-1$

        e.gc.setFont(licFont);

 

        // if the text will not fit the allowed space

        while (!willLicenseTextFit(licenseText, e.gc)) {

          fontSize--;

          licFont = new Font(e.display"Helvetica", fontSize, SWT.NORMAL); //$NON-NLS-1$

          e.gc.setFont(licFont);         

        }

       

        e.gc.drawText(licenseText, 290, 290, true);

      }

    });

 

 

1.       修改背景图片

找到ui/image/下面的kettle_splash.png,替换该图片

2.       修改版本信息

找到e.gc.drawText(versionText, 290, 205, true); 改为e.gc.drawText("海康威视数据交换平台V1.0", 290, 205, true);

3.       修改下面的描述性文字

找到e.gc.drawText(licenseText, 290, 290, true);改为e.gc.drawText("作者:海康", 290, 290, true);

4.       预览效果

修改spoon主界面标题

        

         找到spoon.java下面的

public static final String APP_NAME = BaseMessages.getString(PKG, "Spoon.Application.Name");

修改为要改成的标题

修改spoon界面主菜单栏

Ui/spoon.xul下引用menubar.xul代码,

 

<pen:include src="menubar.xul" ignoreroot="true"/>

menubar.xul为主菜单栏的配置处

修改spoon菜单栏

Spoon菜单栏

配置的地方在spoon.xul下的

<menupopup id="new-file-popup">

    <menuitem label="${Spoon.Menubar.File.NewJob}" command="spoon.newJobFile()" image="${ChefIcon_image}"/>

<menuitem id="menubar-new-trans" label="${Spoon.Menubar.File.NewTrans}" command="spoon.newTransFile()" image="${SpoonIcon_image}"/>

  <menuseparator />

    <menuitem label="${Spoon.Menubar.File.NewDatabaseConn}" command="spoon.newConnection()" image="${CNC_image}"/>

    <menuitem label="${Spoon.Menubar.File.NewSlave}" command="spoon.newSlaveServer()" image="${Slave_image}"/>

  </menupopup>

 

修改转换和job插件列表及图标

Src/kettle-steps.xml是旁边转换插件列表的配置文件处

文件分析如下,这个为表输入的配置

<step id="TableInput">

 <description>i18n:org.pentaho.di.trans.step:BaseStep.TypeLongDesc.TableInput</description>

      <classname>org.pentaho.di.trans.steps.tableinput.TableInputMeta</classname>

      <category>i18n:org.pentaho.di.trans.step:BaseStep.Category.Input</category>

      <tooltip>i18n:org.pentaho.di.trans.step:BaseStep.TypeTooltipDesc.TableInput</tooltip>

      <iconfile>ui/images/TIP.png</iconfile>

</step>

classname为对应的源文件, category为属于哪个分类下,例如tableinput属于“输入”组,tooltip提示信息,iconfile显示图片

主面板布局

spoon.java下的private void addTree()方法内 

 

2.2.           Kettle数据转换流程

Kettle数据转换流程是一个生产者消费者模型,每个步骤为一个节点,每个节点是一个单独的线程,节点和节点之间通过一个阻塞队列传递数据,前一个节点一条一条的往阻塞队列写入,后面节点一条一条的从阻塞队列读取,阻塞队列实现方法在src-core/org/pentaho/di/core/BlockingRowSet.java

 

表输入流程

表输入通过JDBC连接数据库,执行SQL语句后返回一个ResultSet(结果集),如果数据库支持PreparedStatement . setFetchSize(每次返回的多少条记录到结果集),分批将数据返回到结果集中,然后逐条从结果集中读取数据写入到阻塞队列中;如果不支持分批次读取,则一次行将数据返回到ResultSet中,然后逐条读取写入阻塞队列,数据库的操作的方法在src-db/org/pentaho/di/core/database/Database.java中实现。

插入更新流程

2.3.           插件研究

kettle转换步骤工作组件

       这里有四个类构成了这个kettle 步骤/节点,每一个类都有其特定的目的及所扮演的角色。

TemplateStep:  步骤类实现了StepInteface接口,在转换运行时,它的实例将是数据实际处理的位置。每一个执行线程都表示一个此类的实例。

 

TemplateStepData:  数据类用来存储数据,当插件执行时,对于每个执行的线程都是唯一的。执行时里面存储的东西主要包括数据库连接、文件句柄、缓存等等其他东西。

 

TemplateStepMeta元数据类实现了StepMetaInterface接口。它的职责是保存和序列化特定步骤实例的配置,在我们这个例子中,它负责保存用户设置的步骤名称和输出字段的名称。

 

TemplateStepDialog:对话框类实现了该步骤与用户交互的界面,它显示一对话框,通过对话框用户可以自己的喜好设定步骤的操作。对话框类与元数据类关系非常紧密,元数据类可以追踪用户的设置。

 

除了上面的代码,还有一个plugin.xml,它设置好了插件的元数据,定义了步骤在kettle图形工作台中的显示效果。为了更好的让大家理解,我将利用这个步骤设计一个转换流程并执行它。对于插件的开发,我们将从plugin.xml配置文件开始讲起,然后讲讲元数据和对话框类,最后再讲讲步骤类和数据类。

 

书写你自己的plugin.xml

       下面plugin.xml是我们这个插件里面的内容,它的功能是告诉kettle插件的元数据类,插件的名称及描叙,还有需要加载的jar包。想要了解细节,可以查看文章:plug-in loading

<?xml version="1.0" encoding="UTF-8"?>

<plugin

   id="TemplatePlugin"

   iconfile="icon.png"

   description="Template Plugin"

   tooltip="Only there for demonstration purposes"

   category="Demonstration"

   classname="plugin.template.TemplateStepMeta">

   <libraries>

      <library name="templatestep.jar"/>

   </libraries>

</plugin>

 

ID:在kettle插件中必须全局唯一,因为被kettle序列化了,所以不要随便改变

Iconfile: kettle中插件显示的图片,必须是png图片

Description:插件描叙,显示在树形菜单里面。

Tooltip:树形菜单中,鼠标滑过的时候显示的提示信息

Category:插件显示的父目录

Classname:元数据类

Library:指明了插件需要加载所依赖的jar

 

 

插件主要包含类介绍

一、元数据类:

         下面显示了元数据的几个关键的方法,注意元数据类里面用私有成员变量outputField 存储了下一个步骤的输出字段。

 // keep track of the step settings
public String getOutputField()
public void setOutputField(…)
public void setDefault()

// serialize the step settings to and from xml
public String getXML()
public void loadXML(…)

// serialize the step settings to and from a kettle repository
public void readRep(…)
public void saveRep(…)

// provide information about how the step affects the field structure of processed rows
public void getFields(…)

// perform extended validation checks for the step
public void check(…)

// provide instances of the step, data and dialog classes to Kettle
public StepInterface getStep(…)
public StepDataInterface getStepData()
public StepDialogInterface getDialog(…) 

TemplateStepMeta元数据类其实还有很多方面,不过大多被他的父类BaseStepMeta给默认实现了,这些默认的实现足以使我们的元数据类工作良好。想要了解更多,大家可以查查关于StepMetaInteface和BaseStepMeta的kettle官方文档。

 

二、对话框类:

         TemeplateStepDialog为步骤实现了对话框的设置,kettle的用户界面部件是使用的eclipse的swt框架,如果要开发比较复杂的对话框,你还必须熟悉大部分swt代码。 Swt文档大家可以从eclipse上的帮助菜单点击在线获取。在开发过程中,一个对话框对象拥有一个元数据对象,它记录了应该从哪里读取配置?应该把设置好的配置保存在哪里?它仅仅设置了输出字段的名称在我们这个模板步骤里面。一个继承自BaseStepDialog特定的对话框类必须提供open(…)方法,这个方法必须返回这个步骤的名称(发生改变时)或NULL(对话框被取消时)

 

 

三、步骤类:

         步骤类是实际的处理和转换工作的地方。因为大部分样本代码已经由父类BaseStep提供了,大多数插件仅仅关注下面几个特定的方法就行。

 // initialization and teardown
public boolean 
init(…)
public void 
dispose(..)

// processing rows
public void 
run()
public boolean 
processRow(..)

Init()方法在转换执行前被kettle调用,转换必须在所有步骤初始化成功时才真正执行。我们这个模板步骤没有做任何事情,这里仅仅是拿出来让大家了解了解。

dispose()方法是在步骤执行完之后执行(非转换执行完哈),它完成资源的关闭,像文件句柄、缓存等等。

run()方法在实际处理记录集的时候调用。里面其实是个调用processRow()方法处理记录的小循环,当此步骤再没有数据处理或转换被停止时退出循环。

processRow()方法在处理单条记录的时候被调用。这个方法通常通过调用getRow()来获取需要处理的单条记录。 这个方法如果有需要将会被阻塞,例如当此步骤希望放慢脚步处理数据时。processRow()随后的流程将执行转换工作并调用putRow()方法将处理过的记录放到它的下游步骤。

注意:你的步骤可能会变记录的结构,为了安全起见,一定要多熟悉包org.pentaho.di.core.row,特别是类RowMetaInterface和RowDataUtil。

  基类BaseStep对处理的记录提供了第一次访问的标识,在某些代码只执行一次的时候可能非常有用,例如某个费时的查找,其实这就是缓存。

 

四、数据类:

         大多数步骤都需要临时的缓冲或者临时的数据。数据类就是这些数据合适的存放位置。每一个执行线程将得到其拥有的数据类实例,所以它能在独立的空间里面运行。TemplateStepData继承自BaseStepData,作为一个经验法则,不要将non-constant字段放置BaseStepData类里面,如果你必须,请将它最好放置TemplateStepData数据类里面.

 

我们的步骤仅仅使用了一个数据对象来存储记录集输出的结构,没有用到其他的存储介质,例如文件等等。

开发插件实例

1.       kettle-steps.xml下添加如下节点

 

 

<step id="MyTest">

       <description>MyTest</description>

       <classname>mytest.MyTestMeta</classname>

       <category>插件测试</category>

       <tooltip>测试</tooltip>

       <iconfile>ui/images/TIP.png</iconfile>

    </step>

 

 

 

 

 

 

 

 

 

 

2.       创建插件类

MyTest类代码

 

package mytest;

 

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;

 

 

publicclass MyTest extends BaseStep implements StepInterface {

 

public MyTest(StepMeta stepMeta, StepDataInterface stepDataInterface,

        int copyNr, TransMeta transMeta, Trans trans) {

    super(stepMeta, stepDataInterface, copyNr, transMeta, trans);

    // TODO Auto-generated constructor stub

}

 

}

 

 

MyTestData类代码

 

 

package mytest;

 

import org.pentaho.di.trans.step.BaseStepData;

import org.pentaho.di.trans.step.StepDataInterface;

 

publicclass MyTestData extends BaseStepData implements StepDataInterface {

 

}

 

MyTestMeta类代码

 

package mytest;

 

import java.util.List;

import java.util.Map;

 

import org.pentaho.di.core.CheckResultInterface;

import org.pentaho.di.core.Counter;

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.repository.ObjectId;

import org.pentaho.di.repository.Repository;

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.StepInterface;

import org.pentaho.di.trans.step.StepMeta;

import org.pentaho.di.trans.step.StepMetaInterface;

import org.w3c.dom.Node;

 

publicclass MyTestMeta extends BaseStepMeta implements StepMetaInterface {

 

    public MyTestMeta()

    {

       super();

    }

    @Override

    publicvoid setDefault() {

       // TODO Auto-generated method stub

 

    }

 

    @Override

    publicvoid loadXML(Node stepnode, List<DatabaseMeta> databases,

           Map<String, Counter> counters) throws KettleXMLException {

       // TODO Auto-generated method stub

 

    }

 

    @Override

    publicvoid saveRep(Repository rep, ObjectId id_transformation,

           ObjectId id_step) throws KettleException {

       // TODO Auto-generated method stub

 

    }

 

    @Override

    publicvoid readRep(Repository rep, ObjectId id_step,

           List<DatabaseMeta> databases, Map<String, Counter> counters)

           throws KettleException {

       // TODO Auto-generated method stub

 

    }

 

    @Override

    publicvoid check(List<CheckResultInterface> remarks, TransMeta transMeta,

           StepMeta stepMeta, RowMetaInterface prev, String[] input,

           String[] output, RowMetaInterface info) {

       // TODO Auto-generated method stub

 

    }

 

    @Override

    public StepInterface getStep(StepMeta stepMeta,

           StepDataInterface stepDataInterface, int copyNr,

           TransMeta transMeta, Trans trans) {

       // TODO Auto-generated method stub

       returnnull;

    }

 

    @Override

    public StepDataInterface getStepData() {

       // TODO Auto-generated method stub

       returnnull;

    }

 

}

 

 

 

MyTestDialog类代码

 

package mytest;

 

import org.eclipse.swt.SWT;

import org.eclipse.swt.widgets.Display;

import org.eclipse.swt.widgets.Shell;

import org.pentaho.di.trans.TransMeta;

import org.pentaho.di.trans.step.BaseStepMeta;

import org.pentaho.di.trans.step.StepDialogInterface;

import org.pentaho.di.ui.trans.step.BaseStepDialog;

 

publicclass MyTestDialog extends BaseStepDialog implements StepDialogInterface {

   

    public MyTestDialog(Shell parent, Object in,

           TransMeta transMeta, String stepname) {

       super(parent, (BaseStepMeta)in, transMeta, stepname);

       // TODO Auto-generated constructor stub

    }

 

    @Override

    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);

       shell.open();

       shell.setSize(200, 200);

       shell.setText("hello");

       returnnull;

    }

 

}

 

 

TestDialog类外的3个类的代码都是在加入继承基类和接口后更加eclipse插件提示自动生成的,也是我们自己需要实现的方法,TestDialog类中的MyTestDialog方法参数是固定的,根据生成的需要修改下,open函数是要我们自己实现的,运行效果如下

双击MyTest后弹出一个空白的窗口

调用use define java class 插件

kettle中自带的samples\transformations\User Defined Java Class - Calculate the date of Easter.ktr为例

public boolean processRow(StepMetaInterface smi, StepDataInterface sdi) throws KettleException

{

  Object[] r=getRow();//从阻塞队列中获取一个数据对象(一行数据记录)

  if (r==null)//如果没有可获取的数据,代表以处理完成

  {

    setOutputDone();//设置处理完成标志

    return false;//退出循环

  }

 

  if (first) {//第一次进入循环

    //初始化动作

     first=false;//设置为非第一次循环

  }

 /*

*处理函数

*/

  logBasic(r[0].toString());//打印

  putRow(data.outputRowMeta, r);//输出到阻塞队列

  return true;

}

use define java class 插件其实就是一个空插件,然后我们自己来实现processRow函数体,kettle通过while循环来调用processRow函数一行一行的处理数据,流程如下所示

0 0
原创粉丝点击