kettle插件架構

Kettle插件體系

最近公司內有業務系統到數據中心同步的升級改造需求,從各個業務系統收集增量數據到數據中心的數據倉庫平台。因為開發周期短暫,需要快速的響應,開發出可用的產品,所以決定借鑒開源程序Kettle,開發一個文件解析組件,然後利用Kettle平台的大數據組件進行與數據中心大數據平台對接。

數據同步部分是:業務系統(RDBMS)->Kettle(azkaban進行調度)->數據中心,因為Kettle的增量抽取組件經常出現數據不一致等問題,所以目前已更改為:業務系統(RDBMS)->OGG(CDC增量抽取)->數據中心的方式。

本文主要介紹如何擴展Kettle的功能,部分內容來自《Pentaho Kettle解決方案:使用PDI構建開源ETL解決方案》一書,推薦購買閱讀。

架構

我們先看Kettle插件架構。

從功能上看,Kettle內部的對象和外部插件沒有任何區別。因為它們使用的API都是一樣的,它們只是在運行時的載入方式不同。

從Kettle4以後,Kettle內部有一個插件註冊系統,它負責載入各種內部和外部插件。插件有以下兩個標識屬性。

插件類型:由PluginTypeInterface介面定義。例如StepPluginType、JobEntryPluginType、PartitionerPluginType和RepositoryPluginType。

插件ID這是一個字元串數組,用來唯一標識一個插件。因為舊的插件可以被新的插件代替,一個插件可以有多個ID。在大多數情況下,插件只使用一個單一的字元串,如TableInput是「表輸入」步驟的ID,MYSQL是MySQL資料庫類型的ID。

當Kettle環境初始化以後,插件註冊系統首先載入所有的內部對象,Kettle讀取下面的配置文件來載入內部對象,這些配置文件位於Kettle的.jar文件中。

Kettle-steps.xml內部轉換步驟。

Kettle-job-entries.xml:內部作業項。

Kettle-partition-plugins.xml:內部分區類型。

Kettle-database-types.xml內部資料庫類型。

Kettle-repositories.xml內部資源庫類型。

插件註冊系統載入了所有的內部對象後,就要搜索可用的外部插件。通過瀏覽plugins/目錄的各個子目錄下的.jar文件來完成。它搜索特定的Kettle annotations來判斷一個類是否是插件。載入過程將在本章的後面介紹。

因為在內部對象載入後才載入插件,所以插件會替代相同ID的已載入的內部對象。例如,你創建了插件,插件的ID是TableInput,就可以替換Kettle標準的「表輸入」步驟。這個功能可以讓你用插件替換Kettle內置的步驟。可以通過子類繼承方式,直接擴展已有步驟的某些功能。

插件類型

Kettle有下面幾種插件類型(下面的插件是Kettle4.0的插件類型,新版kettle包含了很多新的插件,比如視圖插件、大數據插件等等)。

轉換步驟插件:在Kettle轉換中使用的步驟,用來處理數據行。

作業項插件:在Kettle作業中使用的作業項,用來實現某個任務。

分區方法插件:利用輸入欄位的值指定自己的分區規則。

資料庫類型插件:用來擴展不同的資料庫類型。

資源庫類型插件:可以把Kettle元數據保存為自定義類型或格式。

說明:除了這些類型,還有Spoon類型的插件,可以把功能擴展到Spoon,本書不介紹這個功能。

轉換步驟插件

轉換步驟插件包括了四個Java類,這四個類分別實現四個介面。

StepMetaInterface:這個介面對外 提供步驟的元數據並處理串列化。

StepInterface:這個介面根據上面介面提供的元數據,來實現步驟的具體功能。

StepDataInterface:這個介面用來存儲步驟的臨時數據、文件句柄等。

StepDialogInterface:這個介面是Spoon里的圖形界面,用來編輯步驟的元數據。

接下來,我們介紹這些介面的基本內容。對於每個介面,在一個簡單的「Hello World」例子里提供這些類的相應實現。「Hello World」例子將在數據流里增加一個欄位,欄位名用戶可以自定義,欄位值是」Hello world!「。最後介紹一下如何部署這個例子。

StepMetaInterface

介面org.pentaho.di.trans.step.StepMetaInterface負責步驟里所有和元數據相關的任務。和元數據相關的工作包括:

元數據和XML(或資源庫)之間的序列化和反序列化

getXML()和loadXML()

saveRep()和readRep()

描述輸出欄位

getFields()

檢驗元數據是否正確

Check()

獲取步驟相應的要SQL語句,使步驟可以正確運行

getSQLStatements()

給元數據設置默認值

setDefault()

完成對資料庫的影響分析

analyseImpact()

描述各類輸入和輸出流

getStepIOMeta()

searchInfoAndTargetSteps()

handleStreamSelection()

getOptionalStreams()

resetStepIoMeta()

導出元數據資源

exportResources()

getResourceDependencies()

描述使用的庫

getUsedLibraries()

描述使用的資料庫連接

getUsedDatabaseConnections()

描述這個步驟需要的欄位(通常是一個資料庫表)

getRequiredFields()

描述步驟是否具有某些功能

supportsErrorHandling()

excludeFromRowLayoutVerification()

excludeFromCopyDistributeVerification()

這個介面里還定義了幾個方法來說明這四個介面如何結合到一起。

String getDialogClassName():用來描述實現了StepDialogInterface介面的對話框類的名字。如果這個方法返回了null,調用類會根據實現了StepMetaInterface介面的類的類名和包名來自動生成對話框類的名字。

SetpInterface getStep():創建一個實現了StepInterface介面的類。

StepDataInterface getStepData():創建一個實現了StepDataInterface介面的類。

現在我們看看」Hello World」例子里對SetpMetaInterface介面的實現

HelloworldStepMeta.java

package org.kettlesolutions.plugin.step.helloworld;import java.util.List;import java.util.Map;import org.pentaho.di.core.CheckResult;import org.pentaho.di.core.CheckResultInterface;import org.pentaho.di.core.Const;import org.pentaho.di.core.Counter;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.KettleStepException;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.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.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;@Step( id="Helloworld", name="name", description="description", categoryDescription="categoryDescription", image="org/kettlesolutions/plugin/step/helloworld/HelloWorld.png", i18nPackageName="org.kettlesolutions.plugin.step.helloworld") public class HelloworldStepMeta extends BaseStepMeta implements StepMetaInterface { /** * PKG變數說明了messages包的位置,在messages包里有各種國際化的資源文件。 * 在本章後面經常要看到的BaseMessages.getString()方法,就是根據軟體的國際化 * 設置,從不同的文件中獲取文字。PKG變數通常位於類的最上方,被國際化圖形工具使用, * 通過國際化圖形工具,國際化人員可以編輯不同的國際化資源文件。所以我們會在很多Kettle * 代碼里看見這樣的結構。 */ private static Class<?> PKG = HelloworldStep.class; //for i18n public enum Tag {//field_name用於保存用戶輸入的欄位名:保存「Hello,world!"字元串的欄位名。 field_name, }; private String fieldName; /** * @return the fieldName */ public String getFieldName() { return fieldName; } /** * @param fieldName the fieldName to set */ public void setFieldName(String fieldName) { this.fieldName = fieldName; } /** * checks parameters, adds result to List<CheckResultInterface> * used in Action > Verify transformation * 驗證用戶是否在對話框里輸入了欄位名,並把驗證結果添加到檢驗轉換時出現的問題列表裡。(最好 * 要檢驗用戶輸入的所有選項,而不只是容易出錯的選項) */ public void check(List<CheckResultInterface> remarks, TransMeta transMeta, StepMeta stepMeta, RowMetaInterface prev, String input[], String output[], RowMetaInterface info) { if (Const.isEmpty(fieldName)) { CheckResultInterface error = new CheckResult( CheckResult.TYPE_RESULT_ERROR, BaseMessages.getString(PKG, "HelloworldMeta.CHECK_ERR_NO_FIELD"), stepMeta ); remarks.add(error); } else { CheckResultInterface ok = new CheckResult( CheckResult.TYPE_RESULT_OK, BaseMessages.getString(PKG, "HelloworldMeta.CHECK_OK_FIELD"), stepMeta ); remarks.add(ok);//把驗證結果添加到檢驗轉換時出現的問題列表裡。 } } /** * creates a new instance of the step (factory) * getStep、getStepData和getDialogClassName()方法提供了與這個步驟里其它三個介面之間的橋樑 這個介面里還定義了幾個方法來說明這四個介面如何結合到一起。 String getDialogClassName():用來描述實現了StepDialogInterace介面的對話框類的名字。如果這個方法返回 了null,調用類會根據實現了StepMetaInterface介面的類的類名和包名來自動生成對話框類的名字。 StepInterface getStep():創建一個實現了StepInterface介面的類。 StepInterface getStepData():創建一個實現了StepDataInterface介面的類。 */ public StepInterface getStep(StepMeta stepMeta, StepDataInterface stepDataInterface, int copyNr, TransMeta transMeta, Trans trans) { return new HelloworldStep(stepMeta, stepDataInterface, copyNr, transMeta, trans); } /** * creates new instance of the step data (factory) * getStep、getStepData和getDialogClassName()方法提供了與這個步驟里其它三個介面之間的橋樑 */ public StepDataInterface getStepData() { return new HelloworldStepData(); } /** * getStep、getStepData和getDialogClassName()方法提供了與這個步驟里其它三個介面之間的橋樑 */ @Override public String getDialogClassName() { return HelloworldStepDialog.class.getName(); } /** * deserialize from xml * databases = list of available connections * counters = list of sequence steps * * 下面的四個方法loadXML()、getXML()、readRep()和saveRep()把元數據保存到XML文件或資源庫里, * 或者從XML文件或資源庫讀取元數據。保存到文件的方法利用了像XStream(http://xstream.codehaus.org)這 * 樣的XML串列化技術。 */ public void loadXML(Node stepDomNode, List<DatabaseMeta> databases, Map<String, Counter> sequenceCounters) throws KettleXMLException { fieldName = XMLHandler.getTagValue(stepDomNode, Tag.field_name.name()); } /** * @Override */ public String getXML() throws KettleException { StringBuilder xml = new StringBuilder(); xml.append(XMLHandler.addTagValue(Tag.field_name.name(), fieldName)); return xml.toString(); } /** * De-serialize from repository (see loadXML) */ public void readRep(Repository repository, ObjectId stepIdInRepository, List<DatabaseMeta> databases, Map<String, Counter> sequenceCounters) throws KettleException { fieldName = repository.getStepAttributeString(stepIdInRepository, Tag.field_name.name()); } /** * serialize to repository */ public void saveRep(Repository repository, ObjectId idOfTransformation, ObjectId idOfStep) throws KettleException { repository.saveStepAttribute(idOfTransformation, idOfStep, Tag.field_name.name(), fieldName); } /** * initiailize parameters to default */ public void setDefault() { fieldName = "helloField"; } /** * getFields()方法非常重要,因為它描述了輸出數據行的結構。這個方法需要修改inputRowMeta對象,使這個對象和 * 輸出格式匹配。Spoon和後面的步驟都需要知道這個步驟要輸出哪些欄位。最常見的一種方法,可以給輸出的RowMetaInterface對象 * 添加一個ValueMetaInterface對象。在ValueMetaInterface對象里設置的信息越詳細越好,可以設置的信息包括數據類型、長度、 * 精度、格式掩碼,等等。添加的欄位描述元信息越多,後面生成的SQL就越準確。 */ @Override public void getFields(RowMetaInterface inputRowMeta, String name, RowMetaInterface[] info, StepMeta nextStep, VariableSpace space) throws KettleStepException { String realFieldName = space.environmentSubstitute(fieldName); //值的元數據使用ValueMetaInterface介面描述數據流里的一個欄位。這個介面里定義了欄位的名字、數據類型、長度、精度,等等。下面的例子用於創建一個ValueMetaInterface對象。 ValueMetaInterface field = new ValueMeta(realFieldName, ValueMetaInterface.TYPE_STRING); field.setOrigin(name); inputRowMeta.addValueMeta(field); }}

@Step( id="Helloworld", name="name", description="description", categoryDescription="categoryDescription", image="org/kettlesolutions/plugin/step/helloworld/HelloWorld.png", i18nPackageName="org.kettlesolutions.plugin.step.helloworld")

這段代碼里的@Step annotation用來通知Kettle的插件註冊系統:這個類是一個步驟類型的插件。在annotation里可以指定插件的ID、圖標、國際代的包、本地化的名稱、類別、描述。其中後三項是資源文件里的Key,需要在資源文件里設置真正的值。i18nPackageName指定了資源文件的包名,例如我們這個例子的資源文件位於org/kettlesolutions/plugin/step/helloworld/messages目錄下,en_US(英語,美國)的本地代資源文件是messages_en_US.properties。我們例子里的這個資源文件的內容是:

name=Hello world

description=A verysimple step that adds a new "Helllo world" field to the incomingstream

注意,如果你指定了不存在的分類,Spoon會創建這個分類,並在Spoon的分類樹的最上方顯示這個分類。

最後,annotation里的image標籤指定了插件的圖標。需要32*32像素的PNG文件,可以使用透明樣式。

後面的代碼行說明這個類實現了StepMetaInterface介面。在BaseStepMeta抽象類里定義了這個介面的很多默認實現,可以直接繼承這個抽象類,然後把工作集中在插件特有的功能上。

public class HelloworldStepMeta extends BaseStepMeta implements StepMetaInterface

下面的四個方法loadXML()、getXML()、readRep()和saveRep()把元數據保存到XML文件或資源庫里,或者從XML文件或資源庫讀取元數據。保存到文件的方法利用了像XStream(xstream.codehaus.org)這樣的XML串列化技術。

getFields()方法非常重要,因為它描述了輸出數據行的結構。這個方法需要修改inputRowMeta對象,使這個對象和輸出格式匹配。Spoon和後面的步驟都需要知道這個步驟要輸出哪些欄位。最常見的一種方法,可以給輸出的RowMetaInterface對象添加一個ValueMetaInterface對象。在ValueMetaInterface對象里設置的信息越詳細越好,可以設置的信息包括數據類型、長度、精度、格式掩碼,等等。添加的欄位描述元信息越多,後面生成的SQL就越準確。

值的元數據(Value Metadata)

值的元數據使用ValueMetaInterface介面描述數據流里的一個欄位。這個介面里定義了欄位的名字、數據類型、長度、精度,等等。下面的例子用於創建一個ValueMetaInterface對象。

ValueMetaInterface dateMeta = new ValueMeta(birthdate,ValueMetaInterface.TYPE_DATE);

這個介面也負責轉換數據格式。我們建議使用ValueMetaInterface介面來完成所有數據轉換的工作。例如,日期類型的數據,如果想把它轉換為dateMeta對象里定義的字元串格式,可以用下面的代碼:

//java.util.Date birthdateString birthDateString = dateMeta.getString(birthdate);

ValueMeta類負責轉換。因為有ValueMetaInterface進行數據類型的轉換,所以你不用再去做額外的數據類型轉換的工作。

使用ValueMetaInterface介面時還要注意數據對象是否為Null。從上一個步驟可以接收到一個數據對象和一個描述數據對象的ValueMetaInterface對象。我們要檢查這個數據對象是否為null,在某些情況下如果數據對象為空是不正確的。例如:

數據對象是String類型,有10個空格,Value Metadata需要trim這個字元串。

在Value Metadata里已經定義了從文本文件里載入的數據,要延遲轉換為字元串。所以數據要由二進位的格式(原始數據格式),轉換為字元串格式,然後再轉換為其它格式的數據。

一般使用下面的方法檢查數據對象是否為空:

Boolean n =valueMeta.isNull(valueDate);

重要:要保證傳給ValueMetaInterface對象的數據是在元數據里定義的數據類型。表23-1說明了ValueMetaInterface里定義的數據類型和Java數據類型的對應關係。

Kettle元數據類型和Java里數據類型的對應關係

<table> <tr> <th>Value Meta Type</th> <th>Java Class</th> </tr> <tr> <td>ValueMetaInterface.TYPE_STRING</td> <td>Java.lang.String</td> </tr> <tr> <td>ValueMetaInterface.TYPE_DATE</td> <td>Java.util.Date</td> </tr> <tr> <td>ValueMetaInterface.TYPE_BOOLEAN</td> <td>Java.lang.Boolean</td> </tr> <tr> <td>ValueMetaInterface.TYPE_NUMBER</td> <td>Java.lang.Double</td> </tr> <tr> <td>ValueMetaInterface.TYPE_INTEGER</td> <td>Java.lang.Long</td> </tr> <tr> <td>ValueMetaInterface.TYPE_BIGNUMBER</td> <td>Java.math.BigDecimal</td> </tr> <tr> <td>ValueMetaInterface.TYPE_BINARY</td> <td>Byte[]</td> </tr></table>

行的元數據(Row Meatadata)

行的元數據使用RowMetaInterface介面來描述數據行的元數據,而不是一個列的元數據。實際上,RowMetaInterface的類里包含了一組ValueMetaInterface。另外還包括了一些方法來操作行元數據,倒如查詢值、檢查值是否存、替換值的元數據等。

行的元數據里唯一的規則就是一行里的列的名字必須唯一。當你添加了一個新列時,如果新列的名字和已有列的名字相同,列名後面會自動加上「_2」後綴。如果再加一個同名的列會自動加上」_3「後綴,等等。

因為在步驟里通常是和數據行打交道,所以從數據行里直接取數據會更方便。可以使用很多類似於getNumber()、getString()這樣的方法直接從數據行取數據。例如,銷售數據存儲在第四列里,可以用下面的代碼獲取這個數據:

Double sales = getInputRowMeta().getNumber(rowData,3);

通過索引獲取數據是最快的方式。通過indexOfValue()方法可以獲取列在一行里的索引。這個方法掃描列數組,速度並不快。所以,如果要處理所有數據行,我們建議只查詢一次列索引。一般是在步驟接收到第一行數據時,就查詢列索引,將查詢到的列索引保存起來,供後面的數據行使用。

StepDatainterface

實現了org.pentaho.di.trans.step.StepDataInterface介面的類用來維護步驟的執行狀態,以及存儲臨時對象。例如,可以把輸出行的元數據、資料庫連接、輸入輸出流等存儲到這個對象里。

HelloworldStepData.java

package org.kettlesolutions.plugin.step.helloworld;import org.pentaho.di.core.row.RowMetaInterface;import org.pentaho.di.trans.step.BaseStepData;import org.pentaho.di.trans.step.StepDataInterface;public class HelloworldStepData extends BaseStepData implements StepDataInterface { public RowMetaInterface outputRowMeta;}

StepDialogInterface

實現org.pentaho.di.trans.step.StepDialogInterfac介面的類用來提供一個用戶界面,用戶通過這個界面輸入元數據(轉換參數)。用戶界面就是一個對話框。這個介面里包含了類似open()和setRepository()等的幾個簡單的方法。

Eclipse SWT

Kettle里使用Eclipse SWT作為界面開發包,所以你也要使用SWT來開發對話框窗口。SWT為不同的操作系統Windows、OS X、Linux和Unix提供了一個抽象層。所以SWT的圖形界面和操作系統期貨的程序的界面風格非常相近。

在開始進行SWT開發之前,建議先訪問SWT主面以了解更多的內容eclipse/org/swt。在SWT的網站上,你可以了解到SWT能做出什麼樣的界面效果:

SWT控制項頁,SWT Widgets,給出了你能使用的所有控制項。

SWT樣例頁,SWT Snippets,給出了許多代碼例子。

最好的資源就是Kettle里150個內置步驟的對話框源代碼。

HelloworldStepDialog.java

package org.kettlesolutions.plugin.step.helloworld;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.Control;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.Text;import org.pentaho.di.core.Const;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.ui.core.widget.TextVar;import org.pentaho.di.ui.trans.step.BaseStepDialog;public class HelloworldStepDialog extends BaseStepDialog implements StepDialogInterface { private static Class<?> PKG = HelloworldStepMeta.class; // for i18n // purposes, needed // by Translator2!! // $NON-NLS-1$ private HelloworldStepMeta input; private TextVar wFieldname; public HelloworldStepDialog(Shell parent, Object baseStepMeta, TransMeta transMeta, String stepname) { //初始化元數據對象以及步驟對話框的父類 super(parent, (BaseStepMeta) baseStepMeta, transMeta, stepname); input = (HelloworldStepMeta) baseStepMeta; } public String open() { Shell parent = getParent(); Display display = parent.getDisplay(); shell = new Shell(parent, SWT.DIALOG_TRIM | SWT.RESIZE | SWT.MIN | SWT.MAX); 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, "HelloworldDialog.Shell.Title")); //$NON-NLS-1$ //所有控制項的右側使用一個自定義的百分對對齊。控制項之間的間距使用一個常量,常量值是4像素。 int middle = props.getMiddlePct(); int margin = Const.MARGIN; // Stepname line wlStepname = new Label(shell, SWT.RIGHT); wlStepname.setText(BaseMessages.getString(PKG, "HelloworldDialog.Stepname.Label")); //$NON-NLS-1$ 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); Control lastControl = wStepname; // Fieldname line //創建一個新的標籤控制項,控制項里文本靠右對齊 Label wlFieldname = new Label(shell, SWT.RIGHT); wlFieldname.setText(BaseMessages.getString(PKG, "HelloworldDialog.Fieldname.Label")); //$NON-NLS-1$ //下面一行為控制項設置用戶定義的背景色和字體 props.setLook(wlFieldname); FormData fdlFieldname = new FormData(); fdlFieldname.left = new FormAttachment(0, 0); fdlFieldname.right = new FormAttachment(middle, -margin); fdlFieldname.top = new FormAttachment(lastControl, margin); wlFieldname.setLayoutData(fdlFieldname); wFieldname = new TextVar(transMeta, shell, SWT.SINGLE | SWT.LEFT | SWT.BORDER); props.setLook(wFieldname); wFieldname.addModifyListener(lsMod); FormData fdFieldname = new FormData(); fdFieldname.left = new FormAttachment(middle, 0); fdFieldname.top = new FormAttachment(lastControl, margin); fdFieldname.right = new FormAttachment(100, 0); wFieldname.setLayoutData(fdFieldname); lastControl = wFieldname; // Some buttons wOK = new Button(shell, SWT.PUSH); wOK.setText(BaseMessages.getString(PKG, "System.Button.OK")); //$NON-NLS-1$ wCancel = new Button(shell, SWT.PUSH); wCancel.setText(BaseMessages.getString(PKG, "System.Button.Cancel")); //$NON-NLS-1$ setButtonPositions(new Button[] { wOK, wCancel }, margin, lastControl); // Add listeners lsCancel = new Listener() { public void handleEvent(Event e) { cancel(); } }; lsOK = new Listener() { public void handleEvent(Event e) { ok(); } }; wCancel.addListener(SWT.Selection, lsCancel); wOK.addListener(SWT.Selection, lsOK); lsDef = new SelectionAdapter() { public void widgetDefaultSelected(SelectionEvent e) { ok(); } }; wStepname.addSelectionListener(lsDef); wFieldname.addSelectionListener(lsDef); // Detect X or ALT-F4 or something that kills this window... shell.addShellListener(new ShellAdapter() {//保證了窗口在非正常關閉時,取消用戶的編輯 public void shellClosed(ShellEvent e) { cancel(); } }); // Populate the data of the controls //下面的代碼把數據從步驟的元數據對象里複製到窗口的控制項里 getData(); // Set the shell size, based upon previous time... //窗口的大小和位置將根據窗口的自然屬性、上次窗口大小和位置,以及顯示屏的大小自動設置 setSize(); input.setChanged(changed); shell.open(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) display.sleep(); } return stepname; } /** * Copy information from the meta-data input to the dialog fields. */ public void getData() { wStepname.selectAll(); //為了防止用戶向控制項里輸入空值,Kettle提供了一個靜態方法來檢查宿舍,Const.NVL() wFieldname.setText(Const.NVL(input.getFieldName(), "")); } private void cancel() { stepname = null; input.setChanged(changed); dispose(); } //單擊OK把控制項里用戶輸入的數據都寫入到步驟的元數據對象中。 private void ok() { if (Const.isEmpty(wStepname.getText())) return; stepname = wStepname.getText(); // return value input.setFieldName(wFieldname.getText()); dispose(); }}

窗體布局

如果你看過步驟對話框的源代碼,你就會發現窗體類里有很多煩瑣的代碼。這些代碼確保Kettle可以在各種操作系統下以合適的方式展現窗體。可以發現窗體里的大部分代碼都和布局以及控制項位置有關。

FormLayout是SWT里經常看到的布局方式。程序員可以通過FormLayout指定控制項的百分比、偏移。下面是我們例子里的窗口布局的代碼(HelloworldStepDialog.java)

//創建一個新的標籤控制項,控制項里文本靠右對齊 Label label = new Label(shell, SWT.RIGHT); label.setText(BaseMessages.getString(PKG, "HelloworldDialog.Fieldname.Label")); //$NON-NLS-1$ //下面一行為控制項設置用戶定義的背景色和字體 props.setLook(label); /** * 下面幾行將標籤的左側和對話框的最左側對齊,把標籤的右側放在對話框中間(50%)的左側10個像素 * 的位置。標籤的頂部放在距離對話框頂部25個像素的位置。 */ FormData fdLabel = new FormData(); fdlFieldname.left = new FormAttachment(0, 0); fdlFieldname.right = new FormAttachment(50, -10); fdlFieldname.top = new FormAttachment(0, 25); wlFieldname.setLayoutData(fdLabel);

簡而言之,不要感到痛苦;圖形用戶界面的代碼都比較煩瑣,但代碼並不複雜。

KettleUI元素

除了標準的SWT組件,還可以使用Kettle自帶的一些控制項,Kettle開發人員的工作可以更簡單一些。Kettle自帶的組件包括以下一些。

TableView:這是一個數據表格組件,支持排序、選擇、鍵盤快捷鍵和撤銷/重做,以及右鍵菜單。

TextVar:這是一個支持變數的文本輸入框,這個輸入框的右上角有一個$符號。用戶可以通過」Ctrl+Alt+空格」的方式,在彈出的下拉列表中選擇變數。其他功能和普通的文本框相同。

ComboVar:標準的組合下拉列表,支持變數。

ConditionEditor:過濾行步驟里使用的輸入條件控制項。

另外還有很多常用的對話框幫你完成相應的工作,如下所示:

EnterListDialog:從字元串列表裡選擇一個或多個字元串。左側顯示字元串列表,右側是選中的字元串,並提供把字元串從左側移動到右側的按鈕。

EnterNumberDialog:用戶可以輸入數字

EnterPasswordDialog:讓用戶輸入密碼

EnterSelectionDialog:通過高亮顯示,從列表裡選擇多項

EnterMappingDialog:輸入兩組字元串的映射

PreviewRowsDialog:在對話框里預覽一組數據行。

SQLEditor:一個簡單的SQL編輯器,可以輸入查詢和DDL.

ErrorDialog:顯示異常信息,列出詳細的錯誤棧對話框

Hello World例子對話框

現在我們已經基本了解了SWT以及對話框的布局方式,再看看我們的例子,下面的代碼是HelloWorldStepDialog.java里的例子。

代碼的第一部分是初始化元數據對象以及步驟對話框的父類:

public class HelloworldStepDialog extends BaseStepDialog implements StepDialogInterface { private static Class<?> PKG = HelloworldStepMeta.class; private HelloworldStepMeta input; private TextVar wFieldname; public HelloworldStepDialog(Shell parent, Object baseStepMeta, TransMeta transMeta, String stepname) { //初始化元數據對象以及步驟對話框的父類 super(parent, (BaseStepMeta) baseStepMeta, transMeta, stepname); input = (HelloworldStepMeta) baseStepMeta; }

在下面的open()方法里創建對話框里的所有控制項。SWT使用事件監聽模式,可以為控制項創建各種監聽方法,以響應控制項內容的變化和用戶的動作。

public String open() { Shell parent = getParent(); Display display = parent.getDisplay(); shell = new Shell(parent, SWT.DIALOG_TRIM | SWT.RESIZE | SWT.MIN | SWT.MAX); props.setLook(shell); setShellImage(shell, input); ModifyListener lsMod = new ModifyListener() { public void modifyText(ModifyEvent e) { input.setChanged(); } }; changed = input.hasChanged();

下面代碼說明窗體里的控制項將使用formLayout的布局方式:

FormLayout formLayout = new FormLayout(); formLayout.marginWidth = Const.FORM_MARGIN; formLayout.marginHeight = Const.FORM_MARGIN; shell.setLayout(formLayout);

所有控制項的右側使用一個自定義的百分比對齊:props.getMiddlePct();控制項之間的間距使用一個常量,常量值是4像素。

shell.setLayout(formLayout); shell.setText(BaseMessages.getString(PKG, "HelloworldDialog.Shell.Title")); //$NON-NLS-1$ int middle = props.getMiddlePct(); int margin = Const.MARGIN;

下面的代碼在對話框的最上面添加了一行步驟名稱標籤和輸入文本框:

// Stepname line wlStepname = new Label(shell, SWT.RIGHT); wlStepname.setText(BaseMessages.getString(PKG, "HelloworldDialog.Stepname.Label")); //$NON-NLS-1$ 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); Control lastControl = wStepname;

下面是新增輸出列的列名設置的輸入框:

// Fieldname line //創建一個新的標籤控制項,控制項里文本靠右對齊 Label wlFieldname = new Label(shell, SWT.RIGHT); wlFieldname.setText(BaseMessages.getString(PKG, "HelloworldDialog.Fieldname.Label")); //$NON-NLS-1$ //下面一行為控制項設置用戶定義的背景色和字體 props.setLook(wlFieldname); FormData fdlFieldname = new FormData(); fdlFieldname.left = new FormAttachment(0, 0); fdlFieldname.right = new FormAttachment(middle, -margin); fdlFieldname.top = new FormAttachment(lastControl, margin); wlFieldname.setLayoutData(fdlFieldname); wFieldname = new TextVar(transMeta, shell, SWT.SINGLE | SWT.LEFT | SWT.BORDER); props.setLook(wFieldname); wFieldname.addModifyListener(lsMod); FormData fdFieldname = new FormData(); fdFieldname.left = new FormAttachment(middle, 0); fdFieldname.top = new FormAttachment(lastControl, margin); fdFieldname.right = new FormAttachment(100, 0); wFieldname.setLayoutData(fdFieldname); lastControl = wFieldname;

然後創建兩個按鈕,「確認」和「取消」按鈕,以及按鈕單擊事件的監聽方法,把按鈕放在對話框的最下面:

// Some buttons wOK = new Button(shell, SWT.PUSH); wOK.setText(BaseMessages.getString(PKG, "System.Button.OK")); //$NON-NLS-1$ wCancel = new Button(shell, SWT.PUSH); wCancel.setText(BaseMessages.getString(PKG, "System.Button.Cancel")); //$NON-NLS-1$ setButtonPositions(new Button[] { wOK, wCancel }, margin, lastControl); // Add listeners lsCancel = new Listener() { public void handleEvent(Event e) { cancel(); } }; lsOK = new Listener() { public void handleEvent(Event e) { ok(); } }; wCancel.addListener(SWT.Selection, lsCancel); wOK.addListener(SWT.Selection, lsOK);

下面的代碼做了兩件事情,上部代碼可以保證當步驟名稱或輸出欄位名稱的輸入框在編輯狀態時,單擊「確定」按鈕,正在編輯的內容不會丟失;下部的代碼保證了窗口在非正常關閉時(沒有使用「確定」或「取消」按鈕關閉),取消用戶的編輯。

lsDef = new SelectionAdapter() { public void widgetDefaultSelected(SelectionEvent e) { ok(); } }; wStepname.addSelectionListener(lsDef); wFieldname.addSelectionListener(lsDef); // Detect X or ALT-F4 or something that kills this window... shell.addShellListener(new ShellAdapter() {//保證了窗口在非正常關閉時,取消用戶的編輯 public void shellClosed(ShellEvent e) { cancel(); } });

下面的代碼把數據從步驟的元數據對象里複製到窗口的控制項里:

// Populate the data of the controls getData();

窗口的大小和位置將根據窗口的自然屬性、上次窗口大小和位置,以及顯示屏的大小自動設置。

// Set the shell size, based upon previous time... //窗口的大小和位置將根據窗口的自然屬性、上次窗口大小和位置,以及顯示屏的大小自動設置 setSize(); input.setChanged(changed); shell.open(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) display.sleep(); } return stepname; }

為了防止用戶身控制項里輸入空值,Kettle提供了一個靜態方法來檢查空值,ConstNVL();

/** * Copy information from the meta-data input to the dialog fields. */ public void getData() { wStepname.selectAll(); wFieldname.setText(Const.NVL(input.getFieldName(), "")); }

最後,單擊OK按鈕後,把控制項里用戶輸入的數據都寫入到步驟的元數據對象中:

private void cancel() { stepname = null; input.setChanged(changed); dispose(); } //單擊OK把控制項里用戶輸入的數據都寫入到步驟的元數據對象中。 private void ok() { if (Const.isEmpty(wStepname.getText())) return; stepname = wStepname.getText(); // return value input.setFieldName(wFieldname.getText()); dispose(); }

StepInteface

這個類實現了org.pentaho.di.trans.step.StepInterface介面,這個類讀取上個步驟傳來的數據行,利用StepMetaInterface對象里定義的元數據,逐行轉換和處理上個步驟傳來的數據行,Kettle引擎直接使用這個介面里的很多方法來執行轉換過程,但大部分方法都已經由BaseStep類實現了,通常開發人員只需要重載其中的幾個方法。

Init():步驟初始化方法,用來初始化一個步驟。初始化結果是一個true或者false的Boolean值。如果你的步驟沒有任何初始化的工作,可以不用重載這個方法。

Dispose():如果有需要釋放的資源,可以在dispose()方法里釋放,例如可以關閉資料庫連接、釋放文件、清除緩存等。在轉換的最後Kettle引擎會調用這個方法。如果沒有需要釋放或清除的資源,可以不用重載這個方法。

processRow():這個方法,是步驟實現工作的地方。只要這個方法返回true,轉換引擎就會重複調用這個方法。

下面是HellWorld例子實現的StepInterface介面(HelloworldStep.java)

HelloworldStep.java

package org.kettlesolutions.plugin.step.helloworld; import org.pentaho.di.core.exception.KettleException;import org.pentaho.di.core.row.RowDataUtil;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;/** * BaseStep抽象類已經實現了介面里的很多方法,我們只要覆蓋需要修改的方法即可。 * @author Administrator * */public class HelloworldStep extends BaseStep implements StepInterface { /** * 類的構造函數通常直接把參數傳遞給BaseStep父類。由父類里的方法來構造對象,然後可以直接 * 使用類似transMeta這樣的對象。 * @param stepMeta * @param stepDataInterface * @param copyNr * @param transMeta * @param trans */ public HelloworldStep(StepMeta stepMeta, StepDataInterface stepDataInterface, int copyNr, TransMeta transMeta, Trans trans) { super(stepMeta, stepDataInterface, copyNr, transMeta, trans); // TODO Auto-generated constructor stub } public boolean processRow(StepMetaInterface smi, StepDataInterface sdi) throws KettleException { HelloworldStepMeta meta = (HelloworldStepMeta) smi; HelloworldStepData data = (HelloworldStepData) sdi; /** * getRow()方法從上一個步驟獲取一行數據。如果沒有更多要獲取的數據行,這個方法就會返回null。 * 如果前面的步驟不能及時提供數據,這個方法就會阻塞,直到有可用的數據行。這樣這個步驟的速度就會降低,也會影響 * 其它步驟的速度。 */ Object[] row = getRow(); if (row==null) { /** * setOutputDone()方法用來通知其它的步驟,本步驟已經沒有輸出數據行。下一個步驟如果 * 再調用getRow()方法就會返回null,轉換也不再調用processRow()方法。 */ setOutputDone(); return false; } if (first) { first=false; /** * 從性能上考慮,getRow()方法不提供數據行的元數據,只提供上個步驟輸出的數據。可以使用getInputRowMeta()方法 獲取元數據,元數據只獲取一次即可,所以在first代碼塊里獲取元數據。 如果要把數據傳到下一個步驟,要使用putRow()方法。除了輸出數據,還要輸出RowMetaInterface元數據。 第一行使用clone()方法把輸入行的元數據結構複製給輸出行。輸出行的元數據結構是在輸入行的基礎上增加一個欄位,但 構造輸出行的元數據結構只能構造一次,因為所有輸出數據行的結構都是一樣的,產生了輸出行以後,元數據結構就不能再變化。 所以輸出行的元數據結構在first代碼塊里構造。first是一個內部成員,first代碼塊里的代碼只在處理第一行數據時執行。 下面代碼的最後一行,給輸出數據增加了一個欄位。 */ data.outputRowMeta = getInputRowMeta().clone(); meta.getFields(data.outputRowMeta, getStepname(), null, null, this); } /** * 下面的代碼,把數據寫入輸出流。從性能角度考慮,數據行實現就是Java數組。為了開發方便,可以使用RowDataUtil類提供 * 的一些靜態方法來操作數據。使用RowDatautil靜態方法複製數據,還可以提高性能。 */ String value = "Hello, world!"; Object[] outputRow = RowDataUtil.addValueData(row, getInputRowMeta().size(), value); putRow(data.outputRowMeta, outputRow); return true; }}

解析:

public class HelloworldStep extends BaseStep implements StepInterface {

BaseStep抽象類已經實現了介面里的很多方法,我們只要覆蓋需要修改的方法即可。

類的構造函數通常直接把參數傳遞給BaseStep父類。由父類里的方法來構造對象,然後可以直接使用類似transMeta這樣的對象。

public HelloworldStep(StepMeta stepMeta, StepDataInterface stepDataInterface, int copyNr, TransMeta transMeta, Trans trans) { super(stepMeta, stepDataInterface, copyNr, transMeta, trans); }

getRow()方法從上一個步驟獲取一行數據。如果沒有更多要獲取的數據行,這個方法就會返回null。如果前面的步驟不能及時提供數據,這個方法就會阻塞,直到有可用的數據行。這樣這個步驟的速度就會降低,也會影響其它步驟的速度。

public boolean processRow(StepMetaInterface smi, StepDataInterface sdi) throws KettleException { HelloworldStepMeta meta = (HelloworldStepMeta) smi; HelloworldStepData data = (HelloworldStepData) sdi; Object[] row = getRow(); if (row==null) { setOutputDone(); return false; } if (first) { first=false; data.outputRowMeta = getInputRowMeta().clone(); meta.getFields(data.outputRowMeta, getStepname(), null, null, this); } String value = "Hello, world!"; Object[] outputRow = RowDataUtil.addValueData(row, getInputRowMeta().size(), value); putRow(data.outputRowMeta, outputRow); return true; }

從性能上考慮,getRow()方法不提供數據行的元數據,只提供上個步驟輸出的數據。可以使用getInputRowMeta()方法獲取元數據,元數據只獲取一次即可,所以在first代碼塊里獲取元數據。

setOutputDone()方法用來通知其它的步驟,本步驟已經沒有輸出數據行。下一個步驟如果再調用getRow()方法就會返回null,轉換也不再調用processRow()方法。

Object[] row = getRow(); if (row==null) { setOutputDone(); return false; }

如果要把數據傳到下一個步驟,要使用putRow()方法。除了輸出數據,還要輸出RowMetaInterface元數據。

data.outputRowMeta = getInputRowMeta().clone();

meta.getFields(data.outputRowMeta, getStepname(), null, null, this);

第一行使用clone()方法把輸入行的元數據結構複製給輸出行。輸出行的元數據結構是在輸入行的基礎上增加一個欄位,但構造輸出行的元數據結構只能構造一次,因為所有輸出數據行的結構都是一樣的,產生了輸出行以後,元數據結構就不能再變化。所以輸出行的元數據結構在first代碼塊里構造。first是一個內部成員,first代碼塊里的代碼只在處理第一行數據時執行。下面代碼的最後一行,給輸出數據增加了一個欄位。

下面的代碼,把數據寫入輸出流。

String value = "Hello, world!"; Object[] outputRow = RowDataUtil.addValueData(row, getInputRowMeta().size(), value); putRow(data.outputRowMeta, outputRow);

從性能角度考慮,數據行實現就是Java數組。為了開發方便,可以使用RowDataUtil類提供的一些靜態方法來操作數據。使用RowDatautil靜態方法複製數據,還可以提高性能。

從指定的步驟讀取數據行

如果你想從前面的某個指定的步驟讀取數據行,例如」流查詢「步驟,可以使用getRowFrom()方法。

RowSet rowSet = findInputRowSet(Source Step Name); Object[] rowData = getRowFrom(rowSet); 還可以通過rowSet對象獲得數據行的元數據: RowMetaInterface rowMeta = rowSet.getRowMeta();

把數據行寫入指定的步驟

如果想把數據寫入到某個特定的步驟,例如」過濾「步驟,可以使用putRowTo()方法

RowSet rowSet = findOutputRowSet(Target Step Name); .... putRowTo(outputRowMeta,rowData,rowSet);

很明顯,輸入和輸出的RowSet對象只需獲得一次即可,這樣才更有效率。

把數據行寫入到錯誤處理步驟

如果想讓你的步驟支持錯誤處理,而且元數據類返回的supportErrorHandling()方法返回了true,就可以把數據輸出

到錯誤處理步驟里。下面是使用putError()方法的例子:

Object[] rowData = getRow(); ... try{ ... putRow(...); }catch(Exception e){ if(getStepMeta().isDoingErrorHandling()){ putError(getInputRowMeta(),rowData,errorCode); }else{ throw(e); } }

從例子里可以看到,這段代碼把錯誤的行數、錯誤欄位名、消息、錯誤編碼都傳遞給錯誤處理步驟。

錯誤處理的其他工作都自動完成了。

識別一個步驟拷貝

因為一個步驟可以有多份拷貝同時執行,有時需要識別出正在使用的是哪個步驟拷貝,可以用下面幾個方法。

getCopy():獲得拷貝號。拷貝號可以唯一標識出步驟的一個拷貝,拷貝號的聚會範圍是0-N,N=getStepMeta().getCopies()-1

getUniqueStepNrAcrossSlaves():獲得在集群模式下運行的步驟拷貝號。

getUniqueStepCountAcrossSlaves():獲得在集群模式下運行的步驟拷貝總數。

通過這些方法可以把一個步驟的工作分配給多份拷貝去完成。例如」CSV文件輸入「和」固定文件輸入「步驟里都有並行讀取文件的選項,這樣可以把讀取文件的工作放在多個拷貝里或集群里來完成。

結果反饋

在調用getRow()和putRow()方法時,引擎會自動計算兩類度量值,讀行數和寫行數。這兩類度量值可以在界面或日誌中記錄下來,以監控程序運行的狀態。下面幾個方法用來操作這兩類度量值。

incrementLinesRead():增加從前面步驟讀取到的行數。

incrementLinesWritten():增加定稿到後面步驟中的行數。

incrementLinesInput():增加從文件、資料庫、網路等資源讀取到的行數

incrementLinesOutput:增加寫入到文件、資料庫、網路等資源的行數。

incrementLinesUpdate():增加更新的行數。

incrementLinesSkipped():增加跳過的數據行的行數。

incrementLinesRejected():增加拒絕的數據行的行數。

這些度量值用來說明步驟執行的情況。可以在Spoon的轉換度量面板里看到,也可以存到日誌資料庫表裡。

使用addResultFile()方法,可以把步驟用到的文件保留下來,保存到結果文件列表裡。結果文件列表可以被其它轉換或作業項使用。例如,下面的」CSV文件輸入「的代碼:

ResultFile resultFile = new ResultFile( ResultFile.FILE_TYPE_GENERAL, fileObject,getTransMeta().getName(),getStepName());resultFile.setComment(「File was read by a Csv Input step」);addREsultFile(resultFile);

變數替換

如果輸入框需要支持變數,可以使用environmentSubstritute()方法獲取變數。例如,若想在「Hello World」例子的欄位名輸入框里使用變數,就要把StepMetaInterface里的getFields()方法修改成下面的語句:

String realFiledName = apace. environmentSubstritute(fieldName);

因為步驟本身是一個VariableSpace對象,所以也可以使用下面的語句做變數替換:String

value = environmentSubstritute(meta.getSringWithVariables());

Apache VFS

Kettle里所有操作文件的步驟,都使用Apache VFS系統的方式操作。ApacheVFS不但可以從文件系統讀取文件(如java.io.File),還可以從很多其他來源讀取文件,如FTP伺服器、Z學壓縮文件,等等 。

Apache VFS里的FileObject對象提供了文件的抽象層,然後在Kettle的KettleVFS類里還提供了一系列的靜態方法,來更方便使用FileObject對象,例如下面的代碼 :

FileObject fileObject = KettleVFS.getFileObject(「zip:http://www.example.com/archive.zip!file.txt」);

應該儘可能多地使用KettleVFS,因為它解決了或饒過了很多Apache VFS目前已知的問題。它也增強了SFTP協議。

步驟插件部署

部署之前,要把四個Java源代碼文件編譯為class文件。把編譯好的class文件放到一個Jar包里。可以使用IDE來做這些事情,也可以手工使用ant腳本來做這些事情。

.jar文件應該放在Kettle的plugins/steps目錄下。也可以使用一個子目錄,把所有的依賴的jar包放在插件jar包所在目錄的/lib目錄下,不必再放Kettle的類路徑中(Kettle的libext/目錄)已經有了的jar包。另外可以把多個插件放在一個jar包里。

如果想在IDE里調試插件,可以把插件元數據類的名字放在Kettle_PLUGIN_CLASSES變數里(一個逗號分隔的列表)。關於這個主題的更多信息,請參考pentaho Wiki:How to debug a Kettle 4 plugin。


推薦閱讀:

漫談數據倉庫之維度建模
數據倉庫學習與實踐(二)- 如何做好數據模型設計
如何建設數據倉庫?
如何建立財務數據模型和資料庫?
數據倉庫解決方案 - RedShift 入坑指南

TAG:ETL | 数据仓库 | 商业智能BI |