- ETL是数据抽取(Extract)、转换(Transform)、装载(Load)的过程。
- Kettle是一款国外开源的ETL工具,有两种脚本文件transformation和job,transformation完成针对数据的基础转换,job则完成整个工作流的控制。
- Job:一个作业,由不同逻辑功能的entry组件构成,数据从一个entry组件传递到另一个entry组件,并在entry组件中进行相应的处理。
- Transformation:完成针对数据的基础转换,即一个数据转换过程。
- Entry:实体,即job型组件。用来完成特定功能应用,是job的组成单元、执行单元。
- Step:步骤,是Transformation的功能单元,用来完成整个转换过程的一个特定步骤。
- Hop:工作流或转换过程的流向指示,从一个组件指向另一个组件,在kettle源工程中有三种hop,无条件流向、判断为真时流向、判断为假时流向。
kettle平台是整个系统的基础,包括元数据管理引擎、数据集成引擎、UI和插件管理模块。
元数据管理引擎管理ktr、kjb或者元数据库,插件通过该引擎获取基本信息,主要包括TransMeta、JobMeta和StepMeta三个类。
数据集成引擎包括Step引擎、Job引擎和数据库访问引擎三大部分,主要负责调用插件,并返回相应信息。
UI显示Spoon这个核心组件的界面,通过xul实现菜单栏、工具栏的定制化,显示插件界面接口元素,其中的TransGraph类和JobGraph类是用于显示转换和Job的类。
Kettle的主要包括四大功能模块:
- Chef——工作(job)设计工具 (GUI方式);
- Kitchen——工作(job)执行器(命令行方式);
- Spoon——转换(transform)设计工具 (GUI方式);
- Span——转换(trasform)执行器(命令行方式)。
Kettle的执行分为两个层次:Job和Transformation。两个层次的最主要区别在于数据传递和运行方式。
Transformation(转换)是由一系列被称之为step(步骤)的逻辑工作的网络。转换本质上是数据流。
下图是一个转换的例子,这个转换从文本文件中读取数据,过滤,然后排序,最后将数据加载到数据库。本质上,转换是一组图形化的数据转换配置的逻辑结构。
转换的两个相关的主要组成部分是step(步骤)和hops(节点连接)。
转换文件的扩展名是.ktr。
StepInterface继承体系:
实现StepInterface接口的类,在转换运行时,将是数据实际处理的位置。每个执行线程都表示一个实现StepInterface的实例。
BaseStep实现了StepInterface是各step具体实现类的基类。完成了公用的处理函数,如putRow(),但是对于更具体的processRow()在StepBase的子类中。StepBase的主要成员有
public ArrayList inputRowSets,outputRowSets;
StepBase的子类每次从inputRowSets中取出一行数据,向outputRowSets中写入一行数据。
StepDataInterface继承体系:
实现StepDataInterface接口的类为数据类,当插件执行时,对于每个执行执行的线程都是唯一的。保存于step相关的数据信息,比如行的元数据信息。
StepMetaInterface继承体系
实现了StepMetaInterface接口的类为元数据类。它的职责是保存和序列化特定步骤的实例配置,例如保存步骤的名称、字段名称等,如何生成加载xml或者读写数据库。
StepDialogInterface继承体系
实现了StepDialogInterface接口的类为对话框类,该类实现了该步骤与用户交互的界面,它显示一对话框,通过对话框用户可以根据自己的要求进行步骤的设定。该对话框类与元数据类关系非常密切,对话框里面的配置数据均会保存在元数据类里面。
所有的data均为object对象。步骤与步骤之间以行为单位进行处理,自然需要知道每行的结构,即行元数据。行元数据至少需要包括类型、名称,当然还可能包括字段长度、精度等常见内容。
行元数据不仅在执行的时候需要,而且在转换设置的时候同样需要。每个步骤的行元数据都会保存在.ktr文件或者数据库里面,所以可以根据步骤名称从TransMeta对象中获取行元数据。
行元数据的UML类图结构如下所示,主要有单元格元数据组成行元数据。在现有的版本中,支持的数据类型有String、Date、BigNumber、Boolean、SerializableType、Binary、Integer、Numberic。
程序正常工作需要3种类型的文件:
Kettle并不能做到热插拔,每次添加或者删除插件的时候都需要重启。安装或删除插件,只需要在plugins文件夹下添加或删除对应的文件即可。
Kettle的扩展点包括step插件、job entry插件、Database插件、Partioner插件、debugging插件,这里我们重点介绍step、job entry、database插件。暴露的扩展点如下表所示:
kettle包含的核心包(模块)、类和接口,分为辅助层(底层)、逻辑层和界面层。其对应的源码包结构是core,src, ui的顺序,如下列表所示。
public String getString(String packageName, String key)
{
Object[] parameters = new Object[] {};
return calculateString(packageName, key, parameters);
}
protected String calculateString(String packageName, String key, Object[] parameters)
{
String string=null;
try { string = findString(packageName, langChoice.getDefaultLocale(), key, parameters); } catch(MissingResourceException e) {};
if (string!=null) return string;
//首先从本包内搜寻相应的语言文件
try { string = findString(SYSTEM_BUNDLE_PACKAGE, langChoice.getDefaultLocale(), key, parameters); } catch(MissingResourceException e) {};
if (string!=null) return string;
//如果没有,就从i18n包内搜寻相应的语言
//搜索不到,那么就尝试用默认的第二语言
。。。。。。
return string;
}
核心类是LogWriter.java。在应用中依赖于语言支持包,和文件操作类,其输出方式问文件和控制台端口两种方式,默认是控制台,也可以同时输出到文件中,只要调用不同的构造函数即可。
例如在LogWriter()中,有pentahoLogger.addAppender(consoleAppender);表示日志输出到控制台。
在LogWriter(String filename, boolean exact, int level)
{
this();调用LogWriter()构造函数
this.level = level;
fileAppender = createFileAppender(filename, exact);
addAppender(fileAppender);除了控制台的输出也增加了文件输出。
…
在日志记录中用以下函数记录不同的日志级别。
public static void main(String[] a) throws KettleException {
try {
// Do some initialization of environment variables
//环境初始化
EnvUtil.environmentInit();
List args = new ArrayList(java.util.Arrays.asList(a));
Display display = new Display();
//这是程序的加载界面
Splash splash = new Splash(display);
CommandLineOption[] commandLineOptions = getCommandLineArgs(args);
initLogging(commandLineOptions);
//组件加载
initPlugins();
//用户配置信息加载
PropsUI.init(display, Props.TYPE_PROPERTIES_SPOON); // things to //
// remember...
staticSpoon = new Spoon(display);
//主界面初始化
staticSpoon.init(null);
SpoonFactory.setSpoonInstance(staticSpoon);
staticSpoon.setDestroy(true);
//打开界面并显示
GUIFactory.setThreadDialogs(new ThreadGuiResources());
…
// listeners
//设置监听器
try {
staticSpoon.lcsup.onStart(staticSpoon);
} catch (LifecycleException e) {
……
}
staticSpoon.setArguments(args.toArray(new String[args.size()]));
//程序初始化完毕,进入响应-处理阶段,程序进行正常工作
staticSpoon.start(splash, commandLineOptions);
} catch (Throwable t) {
……
}
// Kill all remaining things in this VM!
System.exit(0);
}
//下面的语句是从kettle的主目录中读取配置文件kettle.properties
Map kettleProperties = EnvUtil.readProperties(Const.KETTLE_PROPERTIES);
System.getProperties().putAll(kettleProperties);
接着同时还要设定(使用语句System.getProperties().put(key,value))一些临时的环境变量(Also put some default values for obscure environment variables in there)。运行环境初始化结束,返回主函数Spoon。
对应engine.src.org.pentaho.di.core包中KettleEnvironment类负责初始化Kettle运行环境,主要包括调用cort.src.org.pentaho.di.core.plugins.PluginRegistry类的init()方法加载组件。
public static void init(boolean simpleJndi) throws KettleException {
//若未曾初始化
if (initialized==null) {
//若不存在则创建一个home文件夹
createKettleHome();
//加载home中kettle.properties文件的内容
EnvUtil.environmentInit();
//Log初始设置:容量大小、超时时长
CentralLogStore.init();
…
//加载Kettle需要用到的变量值,从Kettle-variables.xml文件
KettleVariablesList.init();
initialized = true;
}
}
Spoon.main -> Spoon.initPlugins()-> JobEntryLoader.init()
下面是init()函数体
public static final void init() throws KettleException
{
JobEntryLoader loader = getInstance();
loader.readNatives();
loader.readPlugins();
loader.initialized = true;
}
getInstance()用来获取一个JobEntryLoader对象,进入函数体
public static final JobEntryLoader getInstance()
{
if (jobEntryLoader != null)
return jobEntryLoader;
jobEntryLoader = new JobEntryLoader();
return jobEntryLoader;
}
其中jobEntryLoader是JobEntryLoader的自身静态对象(private static JobEntryLoader jobEntryLoader = null;)可见getInstance()函数创建并返回了JobEntryLoader类的自身静态对象,此对象创建后便唯一存在。
loader.readNatives()读取并解析kettle-config.xml、kettle-job.xmlb等相关xml文件,最终将所有entry都加入到列表jobEntryLoader. pluginList中,实现了组件的加载。
public boolean readNatives()
{
try
{
// annotated classes first
ConfigManager jobsAnntCfg = KettleConfig.getInstance().getManager("jobs-annotation-config");
//读取文件kettle-config.xml文件中关于
//job-xml-config的部分,即标签
Collection jobs = jobsAnntCfg.loadAs(JobPluginMeta.class);
ConfigManager jobsCfg = KettleConfig.getInstance().getManager("jobs-xml-config");
Collection cjobs = jobsCfg.loadAs(JobPluginMeta.class);
jobs.addAll(cjobs);
//加入到pluginList ,类型为JobPlugin.TYPE_NATIVE
for (JobPluginMeta job : jobs)
if (job.getType() != JobEntryType.NONE)
pluginList.add(new JobPlugin(JobPlugin.TYPE_NATIVE, job.getId(), job.getType(), job.getTooltipDesc(), null, null, job.getImageFileName(), job.getClassName().getName(), job.getCategory()));
} catch (KettleConfigException e)
{…
}
return true;
}
loader.readPlugins()读取并解析kettle-config.xml、和kettle-plugin.xml等相关xml文件,最终将一些特别的组件(如dummy组件,这些组件以jar包的形式存放在plugin/jobentries目录下)加载到jo```bEntryLoader.pluginList中,类型为JobPlugin.TYPE_PLUGIN 。
public static final void init(Display d, int t)
{
…
props = new PropsUI(t);
// Also init the colors and fonts to use...
GUIResource.getInstance();
…
}
public Spoon(Display d, Repository rep) {
…
props = PropsUI.getInstance();
…
}
public static final PropsUI getInstance()
{
if (props!=null) return (PropsUI) props;
…
}
PropsUI是Props的子类,props是Props的自身静态对象。
在PropsUI.init()函数中首先实例化props,这其实是加载了一些用户的历史信息,存放在props对象中。然后使用GUIResource.getInstance()来初始化应用程序的颜色、字体、风格等。
初始化spoon时(main函数中语句staticSpoon = new Spoon(display);)在Spoon(display)函数中语句props = PropsUI.getInstance();
staticSpoon = new Spoon(display);
staticSpoon.init(null);
SpoonFactory.setSpoonInstance(staticSpoon);
staticSpoon.setDestroy(true);
GUIFactory.setThreadDialogs(new ThreadGuiResources());
…
// listeners
try {
staticSpoon.lcsup.onStart(staticSpoon);
} catch (LifecycleException e) {
…
}
staticSpoon.start(splash, commandLineOptions);
…
第1-5行是界面的初始化,后面的第8行则是设置监听器,用来监听界面的按键或者鼠标等事件。而第10行是进入事件监听-处理的循环中,直到程序关闭。
staticSpoon = new Spoon(display)获取Spoon对象以及程序初始化所需要的配置信息
public Spoon(Display d, Repository rep) {
this.log = LogWriter.getInstance();
…
//获取系统配置信息和包括用户配置信息
props = PropsUI.getInstance();
shell = new Shell(display);
shell.setText(APPL_TITLE);
staticSpoon = this;
JndiUtil.initJNDI();
SpoonFactory.setSpoonInstance(this);
}
staticSpoon.init(null)函数用来初始化界面
public void init(TransMeta ti) {
//界面布局
FormLayout layout = new FormLayout();
layout.marginWidth = 0;
layout.marginHeight = 0;
shell.setLayout(layout);
…
// Load settings in the props设置配置信息
loadSettings();
…
…//其他设置,主要是热键的设置
initFileMenu();
//主界面
sashform = new SashForm(shell, SWT.HORIZONTAL);
…//主界面的设置
//加入菜单
addMenu();
//加入左边的树面板
addTree();
//加入标签,即上次未关闭的job
addTabs();
…
}
public static final String APP_NAME = BaseMessages.getString(PKG, "Spoon.Application.Name");
public void init(TransMeta ti) {
//对界面布局进行设置
shell.setLayout(layout);
//加入ktr、kjb文件读写监听器
addFileListener(new TransFileListener());
addFileListener(new JobFileListener());
…
//加载初始化一些变量
…
try {
//SwtXulLoader类没有提供src
xulLoader = new SwtXulLoader();
…
} catch (…) {
…
}
//加载部分固定组件及对应监听器
…
}
public void runFile() {
executeFile(true, false, false, false, false, null, false);
}
public void executeFile(boolean local, boolean remote, boolean cluster, boolean preview, boolean debug,Date replayDate, boolean safe) {
//获取当前活跃的Transformation元信息
TransMeta transMeta = getActiveTransformation();
if (transMeta != null)
executeTransformation(transMeta, local, remote, cluster, preview, debug, replayDate, safe);
//获取当前活跃的Job元信息
JobMeta jobMeta = getActiveJob();
if (jobMeta != null)
executeJob(jobMeta, local, remote, replayDate, safe);
}
public TransMeta getActiveTransformation() {
EngineMetaInterface meta = getActiveMeta();
if (meta instanceof TransMeta) {
return (TransMeta) meta;
}
return null;
}
getActiveJob()类的实现同getActiveTransformation()。
如上图,JobMeta和TransMeta都实现了EngineMetaInterface。
上图可见,EngineMetaInterface包含Job、Transformation的整体操作。
public EngineMetaInterface getActiveMeta() {
if (tabfolder == null)
return null;
TabItem tabItem = tabfolder.getSelected();
if (tabItem == null)
return null;
//通过当前活跃的Tab标签确定返回类型。
TabMapEntry mapEntry = delegates.tabs.getTab(tabfolder.getSelected());
EngineMetaInterface meta = null;
if (mapEntry != null) {
if (mapEntry.getObject() instanceof TransGraph)
meta = (mapEntry.getObject()).getMeta();
if (mapEntry.getObject() instanceof JobGraph)
meta = (mapEntry.getObject()).getMeta();
}
return meta;
}
public void executeTransformation(…) {
new Thread() {
…
//利用trans执行代理执行trans
delegates.trans.executeTransformation(transMeta, local, remote, cluster, preview, debug,
replayDate, safe);
…
}.start();
}
public void executeTransformation(…){
…
//获取当前活跃的trans
TransGraph activeTransGraph = spoon.getActiveTransGraph();
…
//将配置设置入executionConfiguration后调用TransGraph实例执行
activeTransGraph.start(executionConfiguration);
…
}
List lastUsedFiles = props.getOpenTabFiles();
for (LastUsedFile lastUsedFile : lastUsedFiles) {
RepositoryMeta repInfo = (rep == null) ? null : rep.getRepositoryInfo();
loadLastUsedFile(lastUsedFile, repInfo, false);
}
首先是从props中获取上次关闭前打开的文件(已经在用户历史信息初始化阶段完成加载),然后逐个将工作流加载到当前应用程序。
private void loadLastUsedFile函数中调用openFile函数来打开job文件,调用语句如下:
if (lastUsedFile.isJob()) {
openFile(lastUsedFile.getFilename(), false);}
Spoon中的openFile函数
public void openFile(String fname, boolean importfile) {
…
loaded = listener.open(root, fname, importfile);
…
}
fname是文件(kjb)路径和名称。
JobFileListrener类中的open函数
public boolean open(Node jobNode, String fname, boolean importfile)
{
Spoon spoon = Spoon.getInstance();
try
{//生成jobMeta,此时里面没有entry
JobMeta jobMeta = new JobMeta(spoon.getLog());
//从kjb文件中加载entry到jobMeta中
jobMeta.loadXML(jobNode, spoon.getRepository(), spoon);
spoon.setJobMetaVariables(jobMeta);
//添加当前job为最近打开文件到菜单中
spoon.getProperties().addLastFile(LastUsedFile.FILE_TYPE_JOB, fname, null, false, null);
spoon.addMenuLast();
//如果job的名字改变,则刷新标签页上的名称
if (!importfile) jobMeta.clearChanged();
jobMeta.setFilename(fname);
//加入到spoon.delegates.jobs中为当前打开的一个job
spoon.delegates.jobs.addJobGraph(jobMeta);
spoon.refreshTree();
spoon.refreshHistory();
return true;
}
catch(KettleException e){
…
}
return false;
}
函数首先是创建一个jobMeta对象,然后通过所给的文件,构建entry并加入到jobMeta中,然后将整个jobMeta加入当前工作流集合spoon.delegates.jobs中。最后刷新主对象树面板。
jobMeta.loadXML(jobNode, spoon.getRepository(), spoon)函数从文件(由spoon.getRepository()提供)加载所有entry,并加入jobMeta。详细如何加载kjb文件请见“打开kjb”一节。
spoon.delegates.jobs.addJobGraph(jobMeta)函数的部分代码如下:
String key = addJob(jobMeta){… jobMap.put(key, jobMeta);…};
JobGraph jobGraph = new JobGraph(spoon.tabfolder.getSwtTabset(), spoon, jobMeta);
spoon.delegates.tabs.addTab(new TabMapEntry(tabItem, tabName, jobGraph,TabMapEntry.OBJECT_TYPE_JOB_GRAPH));
所以此函数不仅是将jobMeta加入到jobs( 列表变量jobMap中)中,还生成相应的JobGraph(标签页)。
打开kjb文件并创建job的过程如下图所示:
public void newJobFile() {
try {
//生成jobMeta对象
JobMeta jobMeta = new JobMeta(log);
…
//将jobMeta加入delegates.jobs中并创建标签页tab
delegates.jobs.addJobGraph(jobMeta);
…
refreshTree();
}
} catch (Exception e) {
…
}
}
和初始化创建那里的openFile()函数差不多,只是openFile()函数还有从xml文件加载组件这部分内容。newJobFile()这里只是新建一个JobMeta类的对象jobMeta,然后调用delegates.jobs.addJobGraph(jobMeta)将jobMeta对象加入到delegates.jobs的jobMap列表中,然后添加相应的标签页。
整个过程函数的调用关系如下:
JobGraph.saveFile()响应鼠标单击保存按钮事件。
public void saveFile() {
spoon.saveFile();
}
Spoon.saveFile()完成保存kjb文件的功能。
public boolean saveFile() {
TransMeta transMeta = getActiveTransformation();
if (transMeta != null)
return saveToFile(transMeta);
//获取当前活动jobMeta
JobMeta jobMeta = getActiveJob();
if (jobMeta != null)
//保存到kjb文件
return saveToFile(jobMeta);
return false;
}
Spoon.saveToFile(EngineMetaInterface meta)函数
public boolean saveToFile(EngineMetaInterface meta) {
if (meta == null)
return false;
boolean saved = false;
…
if (rep != null) {
saved = saveToRepository(meta);
} else {
if (meta.getFilename() != null) {
//保存
saved = save(meta, meta.getFilename(), false);
} else {//另存为
saved = saveFileAs(meta);
}
}
…
return saved;
}
另存为和保存类似,下面拿保存作例子说明
saved = save(meta, meta.getFilename(), false)的调用函数如下:
public boolean save(EngineMetaInterface meta, String fname, boolean export) {
boolean saved = false;
FileListener listener = null;
// match by extension first
//获取扩展名
int idx = fname.lastIndexOf('.');
if (idx != -1) {
String extension = fname.substring(idx + 1);
listener = fileExtensionMap.get(extension);
}
if (listener == null) {
String xt = meta.getDefaultExtension();
listener = fileExtensionMap.get(xt);
}
//如果当前job名称被修改,则更改标签页上的名称
if (listener != null) {
String sync = BasePropertyHandler. getProperty(SYNC_TRANS);
if (Boolean.parseBoolean(sync)) {
listener.syncMetaName(meta, Const.createName(fname));
delegates.tabs.renameTabs();
}//保存到文件
saved = listener.save(meta, fname, export);
…
}
return saved;
}
上面的函数主要作用是获取文件的扩展名,并按照给定的job名称改变标签页的名称。最后保存到文件。
FileListener.save()函数代码如下:
public boolean save(EngineMetaInterface meta, String fname,boolean export) {
Spoon spoon = Spoon.getInstance();
EngineMetaInterface lmeta;
if (export)
{
lmeta = (JobMeta)((JobMeta)meta).realClone(false);
}
else
lmeta = meta;
//保存到文件
return spoon.saveMeta(lmeta, fname);
}
spoon.saveMeta(lmeta, fname)语句的调用函数如下:
public boolean saveMeta(EngineMetaInterface meta, String fname) {
//获取文件名
meta.setFilename(fname);
if (Const.isEmpty(meta.getName()) || delegates.jobs.isDefaultJobName(meta.getName())) {
meta.nameFromFilename();
}
boolean saved = false;
try {//构造XML文件内容
String xml = XMLHandler.getXMLHeader() + meta.getXML();
DataOutputStream dos = new DataOutputStream(KettleVFS.getOutputStream(fname, false));
//将内容写入文件
dos.write(xml.getBytes(Const.XML_ENCODING));
dos.close();
saved = true;
…
return saved;
}
先写入文件的头部(XMLHandler.getXMLHeader()),然后写入job(即标签)。
meta.getXML()调用JobMeta.getXML()函数,详细代码如下:
public String getXML() {
Props props = null;
if (Props.isInitialized())
props = Props.getInstance();
DatabaseMeta ci = getLogConnection();
StringBuffer retval = new StringBuffer(500);
//添加一些标签
retval.append("<").append(XML_TAG).append(">").append(Const.CR); //$NON-NLS-1$
retval.append(" ").append(XMLHandler.addTagValue("name", getName())); //$NON-NLS-1$ //$NON-NLS-2$
…//添加其他的很多标签
//添加所有属于该job的entry信息
retval.append(" ").append(Const.CR); //$NON-NLS-1$一一添加所有属于该job的entry信息
for (int i = 0; i < nrJobEntries(); i++) {
JobEntryCopy jge = getJobEntry(i);
//写入当前entry的信息
retval.append(jge.getXML());
}
retval.append(" ").append(Const.CR); //$NON-NLS-1$
//hop信息
retval.append(" ").append(Const.CR); //$NON-NLS-1$
for (JobHopMeta hi : jobhops) // Look at all the hops
{
retval.append(hi.getXML());
}
retval.append(" ").append(Const.CR); //$NON-NLS-1$
//其他标签
return retval.toString();
}
retval.append(jge.getXML())语句是具体类型的组件调用自己的getXML()函数写入自己的特定的信息,整个过程时序图如下:
JobGraph.runJob()响应鼠标单击运行按钮事件,然后调用Spoon.runFile()执行运行当前job。
public void runFile() {
executeFile(true, false, false, false, false, null, false);
}
public void executeFile(boolean local, boolean remote, boolean cluster, boolean preview, boolean debug, Date replayDate, boolean safe) {
…
//找到当前活动的jobMeta
JobMeta jobMeta = getActiveJob();
if (jobMeta != null)
//运行该jobMeta
executeJob(jobMeta, local, remote, replayDate, safe);
}
public void executeJob(JobMeta jobMeta, boolean local, boolean remote, Date replayDate, boolean safe) {
try {
delegates.jobs.executeJob(jobMeta, local, remote, replayDate, safe);
} catch (Exception e) {…}
}
delegates.jobs.executeJob调用函数如下:
public void executeJob(JobMeta jobMeta, boolean local, boolean remote, Date replayDate, boolean safe) throws KettleException {
…
JobExecutionConfiguration executionConfiguration = spoon.getJobExecutionConfiguration();
…
//运行参数配置对话框
JobExecutionConfigurationDialog dialog = new JobExecutionConfigurationDialog(spoon.getShell(), executionConfiguration, jobMeta);
if (dialog.open()) {
// addJobLog(jobMeta);
//获取当前活动标签(即当前jobMeta)
JobGraph jobGraph = spoon.getActiveJobGraph();
…
// Is this a local execution?
//运行当前job
if (executionConfiguration.isExecutingLocally()) {
jobGraph.startJob(executionConfiguration);
}
…
}
}
先弹出运行参数配置对话框,配置完毕并确定后,接着获取当前活动的标签对象,也就是获取当前活动的工作流。然后运行这个job。
jobGraph.startJob函数代码如下:
public synchronized void startJob(JobExecutionConfiguration executionConfiguration) {
if (job == null) // Not running, start the transformation...
{
// Auto save feature...
//自动保存,如果当前job未保存,则必须先保存为kjb文件
if (jobMeta.hasChanged()) {
if (spoon.props.getAutoSave()) {
…
job = new Job(log, jobMeta.getName(), jobMeta.getFilename(), null);
…
job.start();
…
}
job 是JobGraph类的成员变量用来控制jobMeta的执行,它是一个线程类对象。这个函数首先如果当前jobMeta没有保存,则需要保存;接着新建一个Job类对象将job与当前jobMeta关联起来;最后启动线程job.start()。
上面的过程的时序图如下:
接下来我们看看Job.run()函数
public void run()
{
try
{
stopped=false;
finished=false;
initialized = true;
…
// Run the job, don't fire the job listeners at the end
//执行
result = execute(false);
}
catch(Throwable je)
{…
}}
Result用来保存执行结果
result = execute(false)函数代码如下:
private Result execute(boolean fireJobListeners) throws KettleException
{
…
// Where do we start?寻找开始组件
JobEntryCopy startpoint;
beginProcessing();
startpoint = jobMeta.findJobEntry(JobMeta.STRING_SPECIAL_START, 0, false);
if (startpoint == null) { throw new KettleJobException(Messages.getString("Job.Log.CounldNotFindStartingPoint")); }
JobEntrySpecial jes = (JobEntrySpecial) startpoint.getEntry();
Result res = null;
boolean isFirst = true;
while ( (jes.isRepeat() || isFirst) && !isStopped())
{
isFirst = false;
//进入递归执行组件
res = execute(0, null, startpoint, null, Messages.getString("Job.Reason.Started"));
}
…
return res;
}
递归方式执行job中的entry,自动的从一个entry到另一个entry执行。使用回溯算法。
private Result execute(final int nr, Result prev_result, final JobEntryCopy startpoint, JobEntryCopy previous, String reason) throws KettleException
{
…
// Execute this entry...
JobEntryInterface cloneJei = (JobEntryInterface)jei.clone();
((VariableSpace)cloneJei).copyVariablesFrom(this);
//调用cloneJei自己的execute函数,做和自己功能相关的处理。
// cloneJei是一个有确定类型的JobEntry
final Result result = cloneJei.execute(prevResult, nr, rep, this);
…
// Same as before: blocks until it's done
//递归执行该函数,运行下一个entry
res = execute(nr+1, result, nextEntry, startpoint, nextComment);
…
}
包括三个标签:History、Logging和Job metrics,分别展示不同的信息。
JobGraph.startJob()函数中运行job,运行结束后获取结果信息。
jobGraph.startJob函数代码如下
public synchronized void startJob(JobExecutionConfiguration executionConfiguration) {
if (job == null) // Not running, start the transformation...
{
// Auto save feature...
if (jobMeta.hasChanged()) {
if (spoon.props.getAutoSave()) {
…
job = new Job(log, jobMeta.getName(), jobMeta.getFilename(), null);
…//运行
job.start();
…
// Show the execution results views
//添加结果显示的三个标签页
addAllTabs();
…
}
添加显示结果的三个标签页
public void addAllTabs() {
CTabItem tabItemSelection = null;
if (extraViewTabFolder != null && !extraViewTabFolder.isDisposed()) {
tabItemSelection = extraViewTabFolder.getSelection();
}
//三个标签页
jobHistoryDelegate.addJobHistory();
jobLogDelegate.addJobLog();
jobGridDelegate.addJobGrid();
…
}
public void loadXML(Node jobnode, Repository rep, OverwritePrompter prompter) throws KettleXMLException {
Props props = null;
…
// get job info:
setName( XMLHandler.getTagValue(jobnode, "name") );
…
/*
* read the job entries...读取xml文件中的相应节点
*/
Node entriesnode = XMLHandler.getSubNode(jobnode, "entries"); //$NON-NLS-1$
int tr = XMLHandler.countNodes(entriesnode, "entry");
//逐个读取节点,并根据节点信息,构建JobEntryCopy对象,加
//入到工作流中
for (int i = 0; i < tr; i++) {
Node entrynode = XMLHandler.getSubNodeByNr(entriesnode, "entry", i); //$NON-NLS-1$
JobEntryCopy je = new JobEntryCopy(entrynode, databases, slaveServers, rep);
…
// Add the JobEntryCopy...
addJobEntry(je);
}
//add hop连线
Node hopsnode = XMLHandler.getSubNode(jobnode, "hops"); //$NON-NLS-1$
int ho = XMLHandler.countNodes(hopsnode, "hop"); //$NON-NLS-1$
for (int i = 0; i < ho; i++) {
Node hopnode = XMLHandler.getSubNodeByNr(hopsnode, "hop", i); //$NON-NLS-1$
JobHopMeta hi = new JobHopMeta(hopnode, this);
jobhops.add(hi);
}
…
}
函数首先根据所给的根节点,逐个读取节点子节点,并根据节点信息,构建JobEntryCopy对象,然后加入到jobMeta中。最后加入hop信息。
语句JobEntryCopy je = new JobEntryCopy(entrynode, databases, slaveServers, rep)调用下面函数:
public JobEntryCopy(Node entrynode, List databases, List slaveServers, Repository rep) throws KettleXMLException
{
try
{//获取当前节点代表的组件类型
String stype = XMLHandler.getTagValue(entrynode, "type");
//根据该类型从pluginList 中获取对应的JobPlugin 对象
JobPlugin jobPlugin = JobEntryLoader.getInstance().findJobEntriesWithID(stype);
…
// Get an empty JobEntry of the appropriate class...
//根据jobPlugin获取与之相应的entry类对象,此时的entry并没有任何
//信息,只是一个特定类型的entry
entry = JobEntryLoader.getInstance().getJobEntryClass(jobPlugin);
if (entry != null)
{
//调用该entry自身的loadXML()函数,从文件中加载相应的组件信息
entry.loadXML(entrynode, databases, slaveServers, rep);
…
}
}
JobEntryCopy的构造函数,能够根据节点,构建节点中描述的相应组件实例。
JobEntryLoader.getInstance().getJobEntryClass(jobPlugin)函数下面有说明。
加载entry组件的时序图如下:
public void drop(DropTargetEvent event) {
…
//获取当前坐标
Point p = getRealPosition(canvas, event.x, event.y);
//获取组件类型
try {
DragAndDropContainer container = (DragAndDropContainer) event.data;
//entry是一个string对象,表示该类组件的描述,用来区别不同的组件
String entry = container.getData();
switch (container.getType()) {
case DragAndDropContainer.TYPE_BASE_JOB_ENTRY:
// Create a new Job Entry on the canvas
{//创建新的组件对象并加入到jobMeta中
JobEntryCopy jge = spoon.newJobEntry(jobMeta, entry, false);
…
newJobEntry函数,参数typeDesc是类型描述信息,jobMeta是当前job
public JobEntryCopy newJobEntry(JobMeta jobMeta, String typeDesc, boolean openit) {
return delegates.jobs.newJobEntry(jobMeta, typeDesc, openit);
}
delegates.jobs.newJobEntry函数,参数和上面的函数意义相同
public JobEntryCopy newJobEntry(JobMeta jobMeta, String type_desc, boolean openit)
{
//获取JobEntryLoader的自身静态变量jobEntryLoader
JobEntryLoader jobLoader = JobEntryLoader.getInstance();
JobPlugin jobPlugin = null;
try
{//从jobEntryLoader.pluginList中获取响应的jobPlugin
jobPlugin = jobLoader.findJobEntriesWithDescription(type_desc);
if (jobPlugin == null)
{//一些特殊组件
// Check if it's not START or DUMMY
if (JobMeta.STRING_SPECIAL_START.equals(type_desc)
|| JobMeta.STRING_SPECIAL_DUMMY.equals(type_desc))
{//特殊的组件
jobPlugin = jobLoader.findJobEntriesWithID(JobMeta.STRING_SPECIAL);}
}
if (jobPlugin != null)
{//
// Determine name & number for this entry.
String basename = type_desc;
int nr = jobMeta.generateJobEntryNameNr(basename);
String entry_name = basename + " " + nr; //$NON-NLS-1$
// Generate the appropriate class...
//根据jobPlugin 的信息,构建响应的JobEntry 对象
JobEntryInterface jei = jobLoader.getJobEntryClass(jobPlugin);
…
//创建JobEntryCopy对象,初始化一些相关信息
JobEntryCopy jge = new JobEntryCopy();
jge.setEntry(jei);
jge.setLocation(50, 50);
jge.setNr(0);
//加入到jobMeta中
jobMeta.addJobEntry(jge);
…
大概说说这个函数的整个过程:首先是传入参数jobMeta和String type_desc,一个是当前工作流对象,一个是要添加组件的描述信息。要构建一个entry,必须要从jobEntryLoader.pluginList列表(初始化部分有介绍)中获取和描述信息相符合的JobPlugin对象,然后再根据这个对象生成一个实现JobEntryInterface接口的对象(到此,已经获取了一个特定类型的entry,如本例中的JobEntrySuccess),最后将其装换成JobEntryCopy对象并加入到jobMeta中。
下面是JobEntryInterface jei = jobLoader.getJobEntryClass(jobPlugin) ;语句调用函数getJobEntryClass
public JobEntryInterface getJobEntryClass(JobPlugin sp) throws KettleStepLoaderException
{
…
//根据 sp 的类型获取该类型的组件的加载类cl
switch (sp.getType())
{
case JobPlugin.TYPE_NATIVE:{
cl = Class.forName(sp.getClassname());
}break;
case JobPlugin.TYPE_PLUGIN: {
ClassLoader ucl = getClassLoader(sp);
cl = ucl.loadClass(sp.getClassname());
}break;
…
}
//cl 对应着特定的类型组件,因此下面语句可以返回一个特定的entry对象
JobEntryInterface res = (JobEntryInterface)cl.newInstance();
…
return res;
…
}
总的来说就是根据传入的JobPlugin获取特定类型的entry加载类,然后使用加载类获取该类型的entry对象。
该过程时序图如下:
b)编辑entry
双击工作区域中的组件,弹出组件参数配置对话框,实现entry的编辑。
首先捕捉双击事件
canvas.addMouseListener(new MouseAdapter() {
public void mouseDoubleClick(MouseEvent e) {
clearSettings();
//获取坐标
Point real = screen2real(e.x, e.y);
//尝试根据坐标,从当前的jobMeta中获取entry
JobEntryCopy jobentry = jobMeta.getJobEntryCopy(real.x, real.y, iconsize);
if (jobentry != null) {
//如果当前双击的区域存在组件
if (e.button == 1) {
editEntry(jobentry);
}
…
}}
JobGraph中的editEntry函数
protected void editEntry(JobEntryCopy je) {
spoon.editJobEntry(jobMeta, je);
}
Spoon中的editJobEntry函数
public void editJobEntry(JobMeta jobMeta, JobEntryCopy je) {
delegates.jobs.editJobEntry(jobMeta, je);
}
delegates.jobs.editJobEntry(jobMeta, je)语句调用的函数如下:
public void editJobEntry(JobMeta jobMeta, JobEntryCopy je)
{
try{
…
JobEntryCopy before = (JobEntryCopy) je.clone_deep();
JobEntryInterface jei = je.getEntry();
…
//获取相应的对话框类
JobEntryDialogInterface d = getJobEntryDialog(jei, jobMeta);
if (d != null)
{
if (d.open() != null)
{
// First see if the name changed.
// If so, we need to verify that the name is not already used in the job.
//
…
spoon.refreshGraph();
spoon.refreshTree();
}
}
else{…
}
} catch (Exception e) {…
}
}
根据特定的entry,获取对应的对话框,并且打开对话框,编辑entry。
JobEntryDialogInterface d = getJobEntryDialog(jei, jobMeta)语句调用下列函数
public JobEntryDialogInterface getJobEntryDialog(JobEntryInterface jei, JobMeta jobMeta)
{
//获取对话框类的名称
String dialogClassName = jei.getDialogClassName();
try
{
Class dialogClass;
Class[] paramClasses = new Class[] { spoon.getShell().getClass(), JobEntryInterface.class,
Repository.class, JobMeta.class };
Object[] paramArgs = new Object[] { spoon.getShell(), jei, spoon.getRepository(), jobMeta };
Constructor dialogConstructor;
//获取对话框类
dialogClass = jei.getPluginID()!=null?JobEntryLoader.getInstance().loadClassByID(jei.getPluginID(), dialogClassName):JobEntryLoader.getInstance(). loadClass(jei.getJobEntryType().getDescription(),dialogClassName);
dialogConstructor = dialogClass.getConstructor(paramClasses);
//返回对话框类对象
return (JobEntryDialogInterface) dialogConstructor.newInstance(paramArgs);
} catch (Throwable t)
{…
}
return null;
}
getDialogClassName()用来获取对话框类名,它在JobEntryInterface中定义,在JobEntryBase中实现,代码如下:
public String getDialogClassName()
{
//当前类名
String className = getClass().getCanonicalName();
//加上前缀di.ui.
className = className.replaceFirst("\\.di\\.", ".di.ui.");
//加上后缀Dialog
className += "Dialog";
//最后的className为:di.ui.当前类名Dialog
return className;
}
在函数中根据当前的entry类名及其所在包,返回其对应的Dialog类所在的包及类名。
可见entry编辑时才去查找其对应的参数配置对话框类。
整个过程时序图如下
public void addJobEntry
//添加hop
public void addJobHop
//查找特定的entry
public JobEntryCopy findJobEntry
//查找特定的hop
public JobHopMeta findJobHop
//查找下一个entry,从当前entry到hop指出的entry
public JobEntryCopy findNextJobEntry
//查找前一个entry
public JobEntryCopy findPrevJobEntry
//找到start组件
public JobEntryCopy findStart()
//获取指定的entry
public JobEntryCopy getJobEntry
//获取指定的hop
public JobHopMeta getJobHop
//获取用户选择的entry
public JobEntryCopy getSelected
//写xml文件,将该工作流的信息写入到xml文件,不包括entry的实际内容
public String getXML()
//加载xml文件
public void loadXML()
该类的部分成员函数如下
//添加entry,实际上是添加一个JobEntryCopy对象
public void addJobEntry
//添加hop
public void addJobHop
//查找特定的entry
public JobEntryCopy findJobEntry
//查找特定的hop
public JobHopMeta findJobHop
//查找下一个entry,从当前entry到hop指出的entry
public JobEntryCopy findNextJobEntry
//查找前一个entry
public JobEntryCopy findPrevJobEntry
//找到start组件
public JobEntryCopy findStart()
//获取指定的entry
public JobEntryCopy getJobEntry
//获取指定的hop
public JobHopMeta getJobHop
//获取用户选择的entry
public JobEntryCopy getSelected
//写xml文件,将该工作流的信息写入到xml文件,不包括entry的实际内容
public String getXML()
//加载xml文件
public void loadXML()
private JobEntryInterface entry;当前组件,具体entry,执行入口
private int nr;副本数,一个编辑区里可以出现多个相同组件
private boolean selected;是否被选择
private Point location;图标位置,工作区中的坐标
private boolean draw;
private long id;
相关函数如下:
//写xml文件,主要是写entry的共性部分,比如坐标
public String getXML
//判断是否被选择
public boolean isSelected
//设置entry对象
public void setEntry(JobEntryInterface je)
//获取坐标
public Point getLocation
//设置坐标
public void setLocation(Point pt);
JobEntryLoader
这个类作用是加载所有的entry,entry的来源有两种,一种是内置的,一种是插件形式。
JobEntryLoader的一些重要成员变量如下:
JobEntryLoader的一些重要成员函数如下:
加载所有组件
public static final void init() throws KettleException
包括加载内置组件和加载插件
加载内置组件
public boolean readNatives()
内置组件在xml文件中定义。
加载插件
public boolean readPlugins()
插件在plugin目录下,一个plugin为一个jar
根据特定的plugin返回相应的entry
public JobEntryInterface getJobEntryClass(String desc) throws
因为工作流中真正执行单元功能的是entry,pluginList中储存的并非entry,只是一些加载entry所需要的信息(比如加载类、entry类型、ID等),所以我们必须根据plugin加载相应的entry并返回。
JobEntryInterface
JobEntryInterface是Job Entry插件的主要实现接口,是所有具体entry必须实现的接口。需要实现的功能主要是获取entry的一些共性信息,比如名称、ID、类型、描述信息、是否是特殊entry、参数配置对话框类名、写xml、读xml等。
每个具体engine.src.org.pentaho.di.job.entries包下的 entry类需要实现的接口,包含execute()方法。
几个比较重要的函数如下:
//获取该entry对应的参数配置对话框类
public String getDialogClassName()
//读kjb中标签
public void loadXML
//写kjb中的< entry>标签
public String getXML
//执行entry
public Result execute(Result prev_result, int nr, Repository rep, Job parentJob) throws KettleException;
//克隆函数
public Object clone()
//写kjb文件函数
public String getXML()
//读取kjb文件
public void loadXML(Node entrynode, List databases,List slaveServers, Repository rep)
throws KettleXMLException
//执行该组件
public Result execute(Result previousResult, int nr, Repository rep,Job parentJob) throws KettleJobException
public class Job extends Thread implements VariableSpace, NamedParams
其主要功能是负责作业的执行。
该类的一些重要成员变量如下:
private JobMeta jobMeta//当前工作流
private Job parentJob//
private Result result//执行结果保存
private boolean finished//结束标志
线程的run函数
public void run()
执行entry
public Result execute(Boolean)
函数最终一一地从jobMeta中取出entry并调用entry它自己的execute方法。
m)Result
每一个jobEntryInterface的实现类在完成相应功能时,返回结果的类型。
Result中也可以有处理数据,这些处理数据可以作为下一个Job项目(JobEntry)的输入。但是容量受内存容量限制。
主要成员变量:
private boolean result;执行是否出现异常
private int exitStatus; 执行结果状态
private List rows;一个jobEntry完成处理后的数据(若存在)
private Map resultFiles;
TransMeta transMeta = getTransMeta(rep);
……
Trans trans = new Trans(transMeta);
……
trans.execute(args);
b)Trans类execute( )
具体执行前需要进行准备工作
public void execute(String[] arguments) throws KettleException{
prepareExecution(arguments);
startThreads();
}
for (int i=0;i nextSteps = transMeta.findNextSteps(thisStep);
int nrTargets = nextSteps.size();
for (int n=0;n
- 根据TransMeta的step信息生成相应的StepMetaDataCombi(即steps)信息,加到steps列表中。
StepMetaDataCombi combi = new StepMetaDataCombi();
combi.stepname = stepMeta.getName();
combi.copy = c;
combi.stepMeta = stepMeta;
combi.meta = stepMeta.getStepMetaInterface();
StepDataInterface data = combi.meta.getStepData();
combi.data = data;
……
StepInterface step=combi.meta.getStep(stepMeta, data, c, transMeta, this);
在step初始化时,会把Trans中的List的相应的rowset加入到step的inputRowSets,和outputRowSets中。
combi.step = step;
steps.add(combi);
- Trans类startThreads( )
打开了所有的step线程,核心代码如下:
for (int i=0;i
- Step执行
实现StepInterface的不同的step各个功能个不一样,但是它们之间也有一定的规律性。下图只列举了两个step,(TextInput)文本输入和Uniquerow(去重)。
- 启动
每一个具体的step启动线程时,自动调用run函数,它们统一调用基类的静态方法
public void run(){
BaseStep.runStepThread(this, meta, data);
}
- 处理
基类BaseStep采取了统一的处理方式,调用子类processRow以行为单位处理,核心代码如下:
while (stepInterface.processRow(meta, data) && !stepInterface.isStopped());
processRow( )通用过程是:调用基类BaseStep 的getRow( )得到数据,对一行数据处理,处理之后调用基类putRow( )方法数据保存至outputRowSets(即next step的inputRowSets)
- 元数据与数据关系。
Trans中的ETL过程(每个step)以行为单位处理,其中行的元数据信息RowMeta和数据信息统一保存在RowSet对象中。
在RowSet中RowMeta的成员的调试结果如下。可见rowMeta储存了每列数据的名称和类型。第一列列名flag,数据是长度为1的String;第二列列名id…

RowSet的数据信息在queArray队列中,调试结果如下:可以看出第一个数据元素是一个Object包含了3列,数据内容为(N,1,a…)

- 相关类和接口
- JobEntryTrans
实现了JobEntryInterface的execute()方法,被job执行。由JobEntryTrans实例化Trans,并执行。
- TransGraph
当点击trans面板的run时,由TransGraph实例化Trans,并执行。
Trans主要成员有:
private TransMeta transMeta;
private Repository repository;
private Job parentJob;
private Trans parentTrans;
private List rowsets;
private List steps
其中最重要的是rowsets、steps。rowsets保存了所有hop对应的行元数据和数据信息。List steps封装了一个step的主要内容。
- TransMeta
描述了整个Trans的元数据信息。 主要的属性成员有:
private List steps;
private List hops;
private String name;
private Result previousResult;上一个jobentry的执行结果。
private List resultRows;这次trans执行后的数据结果。
private List resultFiles;
resultRows成员将作为result比部分返回多行的元数据和数据(如果有的话)需要返回数据结果时。把resultRows加入Result结果的rows列表,并返回。
- StepMetaDataCombi
提取了一个step所需的主要信息,把插件的主要实现类全部存储在这个类中,方便集中调用。
public class StepMetaDataCombi
{
public StepMeta stepMeta;
public String stepname;
public int copy;
public StepInterface step;
public StepMetaInterface meta;
public StepDataInterface data;
}
- TransHopMeta
描述hop信息。
- StepMeta
描述step的公有基本信息(stepid,stepname),对于每一个具体的step,由成员变量StepMetaInterface step来描述。
- StepInterface
主要成员函数:
processRow()对一行的数据处理。
putRow()把处理后的数据放入下一个step的inputrowsets中。
- StepBase
实现了StepInterface是各step具体实现类的基类。完成了公用的处理函数,如putRow(),但是对于更具体的processRow()在StepBase的子类中。StepBase的主要成员有
public ArrayList inputRowSets,outputRowSets;
StepBase的子类每次从inputRowSets中取出一行数据,向outputRowSets中写入一行数据。
- StepDataInterface
与step相关的数据信息。比如行的元数据信息。StepMetaInterface的实现类是与具体step相关的元数据信息,与StepMeta配合使用,共同描述具体step的元数据信息。
- RowSet

RowSet类中包含源step,目标step和由源向目标发送的一个rowMeta和一组data。其中data数据是以行为单位的队列(queArray)。一个RowSet作为此源step的outputrowsets的一部分。同时作为目标step的inputRowsets一部分。源Step每次向队列中写一行数据,目标step每次从队列中读取一行数据。
- RowMetaAndData
public class RowMetaAndData implements Cloneable{
private RowMetaInterface rowMeta;//行的元数据,描述了每行的数据名字,数据类型。
private Object[] data;//数据
}
- StepInitThread类
Step初始化线程包装类,使用多线程,调用所有StepInterface实现类的Init函数。
- RunThread类
步骤处理线程包装类,这个类能够处理异常并将其记录到日志中。同时,也能够在异常发生或者执行结束后,记录相关内容、关闭相关资源。
- 菜单加载
有三种菜单:程序工具栏菜单、标签页上的菜单和右键菜单。
- 程序菜单栏
这种菜单在界面初始化时就被生成并显示到程序的菜单栏上。
调用Spoon.init(TransMeta ti)实现菜单的初始化。
public void init(TransMeta ti) {
…
initFileMenu();
...
addMenu();
addTree();
// addCoreObjectsExpandBar();
addTabs();
…
}
Spoon.addMenu()函数
public void addMenu() {
…
try {
//从ui/menubar.xul文件中生成菜单栏
menuBar = XulHelper.createMenuBar(XUL_FILE_MENUBAR, shell, new XulMessages());
List ids = new ArrayList();
//菜单项
ids.add("trans-class");
…
this.menuMap = XulHelper.createPopupMenus(XUL_FILE_MENUS, shell, new XulMessages(), ids);// createMenuBarFromXul();
} catch (Throwable t) {…
}
//监听器
addMenuListeners();
//添加最近打开菜单项
addMenuLast();
}
XulHelper.createMenuBar(XUL_FILE_MENUBAR, shell, new XulMessages())函数代码如下:
public static MenuBar createMenuBar(String menuFile, Shell shell,Messages xulMessages) throws KettleException
{
// first get the XML document
try
{
URL xulFile = getAndValidate(menuFile);
Document doc = XMLHandler.loadXMLFile(xulFile);
//此处从xul文件中加载菜单项
MenuBar menuBar = MenuHelper.createMenuBarFromXul(doc, shell, xulMessages);
shell.setMenuBar((org.eclipse.swt.widgets.Menu) menuBar.getNativeObject());
return menuBar;
} catch (IOException e){
…
}
}
Xul文件格式如下(项目的在根目录ui/menubar.xul)
取其中的一个标签来说明:
标签表示视图(view)菜单 是其下拉菜单,< menuitem >是一个个菜单项
- 标签页上的菜单
因为一个JobGraph对象表示一个标签页,所以我们可以在JobGraph类中找到与菜单相关的成员变量:private XulToolbar toolbar;
它在下面函数中被初始化加载菜单项
private void addToolBar() {
try {
toolbar = XulHelper.createToolbar(XUL_FILE_JOB_TOOLBAR, JobGraph.this, JobGraph.this, new XulMessages());
…
}
}
XUL_FILE_JOB_TOOLBAR指明的文件是程序根目录下的ui.job-toolbar.xul,其内容如下:
…
Kettle资源文件及配置文件
- kettle.properties
一般存放在应用程序的工作主目录C:\Documents and Settings\Administrator.kettle下里面有几个文件,存放用户对于系统的喜好配置以及历史信息,这些信息会在启动时被读取。其中kettle.properties较为重要,它在程序启动环境初始化过程被读取;和.languageChoice文件,它指明了当前的语言设定。


格式如下:
#Language Choice
#Tue Apr 12 16:13:38 CST 2011
#这个是当前默认语言
LocaleDefault=zh_cn
#这个是第二语言
LocaleFailover=en_us
- kettle-config.xml
在src.org.pentaho.di.core.config包中
整个文件分为5个部分(5个标签)提供程序初始化时加载组件的一些必要信息。5个部分包括对job类型组件的加载配置说明、step类型组件的加载配置说明和plugin类型组件的加载配置说明。
比如加载entry组件需要下面的信息
org.pentaho.di.core.config.DigesterConfigManager
标签指明处理加载entry的类;
标签指明组件信息所在的文件;
标签指明加载entry的规则;
标签指明处理的起点节点(在标签中指明的xml文件中)。
- kettle-job.xml
在src包下
所有job组件都必须在此注册,每一个标签就是一个组件,提供加载一个组件的必要信息。
ognl:@org.pentaho.di.job.JobEntryType@WAIT_FOR_FILE
org.pentaho.di.job.entries.waitforfile.JobEntryWaitForFile
ognl:@org.pentaho.di.job.JobEntryCategory@FILE_MANAGEMENT.getName()
ognl:@org.pentaho.di.job.entry.Messages@getString("JobEntry.WaitForFile.Tooltip")
ui/images/WFF.png
…其他job
标签表示一个特定id的组件
标签表明了该组件的类型,这个类型必须出现在org.pentaho.di.job.JobEntryType枚举中。
标签指明该组件的实现类
标签指明当鼠标移动到组件图标上时出现的提示信息
标签指明该组件的图标文件。
-
Message文件
在你使用的类所在的包下,子包Message中。
格式为:名称 = 值
不同的语言我们创建不同的message文件。名称格式为Message_<语言简称>_<国家简称>.properties,例如中文中国:Message_zh_CN. Properties。
-
.kjb文件格式
.kjb是Job元数据文件的后缀。主要有三部分构成:job基本信息,job的entris组件信息和组件之间的链接hop信息。标签为job。
-
-
entries标签
job的信息有两部分:
公有信息:每个entry都具有的信息。如name,description,jobentrytype等,由JobEntryBase保存。
私有信息:每个具体step继承了JobEntryBase并各自特有的信息,如开始entry的xml中其他信息,如下图所示。

每个标签内容对应界面层元素如下图

-
hops标签
Hops信息主要有:源和目标entry信息;链接的状态,是否开启;链接的类型:条件执行还是无条件执行等。如下图所示:

-
.ktr文件格式
一个UniqueRiws.ktr 文件的Transformation顶层有Transformation信息 , 信息。信息组成。

-
info标签
节点包括了一个Trans的信息。如trans名字,trans描述信息,日志设置信息,设置的最大RowSet数量,修改时间。

对应的界面层设置是:

-
-
step标签
节点信息描述了一个节点的信息。包括两部分信息:
每个step都有的信息:如stepname,step类型信息,所在面板位置信息,
具体step特有的信息:如去重是否对重复的行计数,以及计数变量名。
文本输出的格式,分隔符,数据名和数据类型信息。
以下是去重step对应的内容。

对应的GUI设置为:

-
图标
package org.pentaho.di.laf;
public class OverlayPropertyHandler implements PropertyHandler {
protected static final String propFile = "ui/laf.properties";
……
}
使用:
final Image kettle_image = ImageUtil.getImageAsResource(display,
BasePropertyHandler.getProperty("splash_image"));
你可能感兴趣的:(大数据,数据库,数据库,前端)