kettle5.3扩展step插件支持元数据注入

1. 前言

虽然说kettle提供了可视化的操作界面,但对于一些共有相同功能的ETL流程我们不希望每次都在spoon上面拖拉组件去创建流程,感觉比较繁琐,所以在基于kettle做二次开发的时候希望可以把相同类型的流程提取成公共的模板,每次只需要把不同的参数传入到插件里面即可实例化成不同的模板作业,kettle的插件式设计很符合我们的需求,它提供的变量设置、获取以及元数据注入组件基本能满足工们所有的功能。
变量与元数据注入组件一般是结合起来使用才能使用作业设计得通用一点(所有参数都可以做到动态化),使用表输入组件,其中sql记录数据限制可以通过选取一个变量来设置值:

kettle5.3扩展step插件支持元数据注入_第1张图片

但是对于其它一些属性,如步骤名称数据库连接等就只能配置死了,这时候我们可以利用元数据注入插件把其余或者全部参数设置进去,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HXBXkMZr-1659423362092)(https://img-blog.csdn.net/20170710101022372?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY3ptYWNk/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)]

当然了,元数据注入只管把参数注入到其它插件,至于注入的参数值是什么一般还是通过获取变量来从外部获得到。

2. 了解ETL元数据注入步骤

元数据注入step可以把元数据注入到模板转换中的step中,从而不用在step设置界面中静态地设置元数据,做到运行时动态,所以可以解决重复ETL工作。
在本文基于kettle5.3版本,也只有一小部分step组件默认支持元数据注入而已,可以看一下StepMetaInjectionInterface接口的实现类:

kettle5.3扩展step插件支持元数据注入_第2张图片

在图中还有一些是我们在原来的基础上增加实现的。其实kettle中插件式的设计,每个step都有对应的xxxMeta类与之对应,这个类就保存着step的所有
所有元数据,ETL元数据注入就是通过设置xxxMeta类中的属性值把元数据注入到运行时的step中,使作业具有不同的行为。

3. 扩展step支持元数据注入

要想一些默认没支持元数据注入的step实现支持该功能其实很方便,不需要大改动原码,下面以REST Client为示例进行扩展。

3.1 创建RestMetaInjection类

根据名称大概可以找到REST Client步骤相关源码类为RestRestMetaRestData,在同package下创建RestMetaInjection类,该类需要实现StepMetaInjectionInterface接口,可以看到子类需要实现接口的3个方法:

//该接口是获取所有需要注入的元数据实体,例如'步骤名称'就是一个需要注入的元数据实体
public List getStepInjectionMetadataEntries() throws KettleException;
//该接口是实现注入的具体实现,主要是初始化一些元数据的值
public void injectStepMetadataEntries( List metadata ) throws KettleException;
//以标准的方式提取出step的元数据,这个接口的逻辑我们可以不实现
public List extractStepMetadataEntries() throws KettleException;

3.2 为RestMetaInjection类添加构造函数

既然最终都是为了操作元数据,那么我样需要拿到RestMeta对象,因此添加一个构造函数来接收:

public RestMetaInjection(RestMeta meta) {
		this.meta = meta;
	}

3.3 创建Entry枚举

Entry枚举实现了StepMetaInjectionEntryInterface接口,它描述了需要注入的元数据有哪些,方便下面接口进行具体的注入操作,我们在编写这个枚举时需要参照对应的界面代码类RestDialog,从界面中找到哪些输入元素是我们将要实现注入的,这些根据一些Label的message信息应该都很容易找得到,代码如下:

public enum Entry implements StepMetaInjectionEntryInterface {
		
		PARAMETERS(ValueMetaInterface.TYPE_NONE, BaseMessages.getString(PKG, "RestDialog.Parameters.Label")),
		PARAMETER(ValueMetaInterface.TYPE_NONE, "one parameter"),   //对应grid中的一行记录
		PARAMETER_FIELD(ValueMetaInterface.TYPE_STRING,	BaseMessages.getString(PKG, "RestDialog.ColumnInfo.ParameterField")), //对应一行的参数字段column
		PARAMETER_NAME(ValueMetaInterface.TYPE_STRING,BaseMessages.getString(PKG, "RestDialog.ColumnInfo.ParameterName")), 
		//上面几个描述了参数的tab中的表格属性,最终需要注入的只是PARAMETER_FIELD与PARAMETER_NAME,但其它两个只是需要在元数据注入step中显示
		
		MOTHEDFIELD(ValueMetaInterface.TYPE_STRING,BaseMessages.getString(PKG, "RestDialog.MethodField.Label")), //注入method
		BODY(ValueMetaInterface.TYPE_STRING,BaseMessages.getString(PKG, "RestDialog.Body.Label")),  //注入body
		APPLICATIONTYPE(ValueMetaInterface.TYPE_STRING,BaseMessages.getString(PKG, "RestDialog.ApplicationType.Label")), //注入applicationType
		
		HTTPLOGIN(ValueMetaInterface.TYPE_STRING,BaseMessages.getString(PKG, "RestDialog.HttpLogin.Label")), //注入Http验证登录
		HTTPPASSWORD(ValueMetaInterface.TYPE_STRING,BaseMessages.getString(PKG, "RestDialog.HttpPassword.Label")),//注入验证密码
		PROXYHOST(ValueMetaInterface.TYPE_STRING,BaseMessages.getString(PKG, "RestDialog.ProxyHost.Label")),//注入代理服务器
		PROXYPORT(ValueMetaInterface.TYPE_STRING,BaseMessages.getString(PKG, "RestDialog.ProxyPort.Label")),//注入代理端口
		
		TRUSTSTOREFILE(ValueMetaInterface.TYPE_STRING,BaseMessages.getString(PKG, "RestDialog.TrustStoreFile.Label")),//注入Trust store file
		TRUSTSTOREPASSWORD(ValueMetaInterface.TYPE_STRING,BaseMessages.getString(PKG, "RestDialog.TrustStorePassword.Label")), //注入Trust store password 

		HEADERS(ValueMetaInterface.TYPE_NONE,BaseMessages.getString(PKG, "RestDialog.Headers.Label")),
		HEADER(ValueMetaInterface.TYPE_NONE,BaseMessages.getString(PKG, "one header")),
		HEADER_FIELD(ValueMetaInterface.TYPE_STRING,BaseMessages.getString(PKG, "RestDialog.ColumnInfo.Field")),
		HEADER_NAME(ValueMetaInterface.TYPE_STRING,BaseMessages.getString(PKG, "RestDialog.ColumnInfo.Name")),
		;
		//同参数的注入,对于一个表格的注入参考这样的方式即可


		private int valueType;
		private String description;

		private Entry(int valueType, String description) {
			this.valueType = valueType;
			this.description = description;
		}

		@Override
		public int getValueType() {
			return this.valueType;
		}

		@Override
		public String getDescription() {
			return this.description;
		}

		public static Entry findEntry(String key) {
			return Entry.valueOf(key);
		}
	}

3.4 实现getStepInjectionMetadataEntries方法

getStepInjectionMetadataEntries是获取所有step中需要注入的StepInjectionMetaEntry,然后显示在ETL元数据注入step的注入元数据树表格中,该方法的逻辑就是根据上面Entry枚举进行组装List,参考以下的代码:

@Override
	public List getStepInjectionMetadataEntries() throws KettleException {
		List all = new ArrayList();
		Entry[] topEntries = new Entry[] { Entry.MOTHEDFIELD, Entry.BODY, Entry.APPLICATIONTYPE, Entry.HTTPLOGIN,
				Entry.HTTPPASSWORD, Entry.PROXYHOST, Entry.PROXYPORT ,Entry.TRUSTSTOREFILE,Entry.TRUSTSTOREPASSWORD};
		StepInjectionUtil.addTopEntry(topEntries, all);  //设置一些顶级的注入实体

		StepInjectionMetaEntry parameters = new StepInjectionMetaEntry(Entry.PARAMETERS.name(),
				Entry.PARAMETERS.getValueType(), Entry.PARAMETERS.getDescription());
		StepInjectionMetaEntry parameter = new StepInjectionMetaEntry(Entry.PARAMETER.name(),
				Entry.PARAMETER.getValueType(), Entry.PARAMETER.getDescription());
		Entry[] subEntries1 = new Entry[] { Entry.PARAMETER_FIELD, Entry.PARAMETER_NAME };
		StepInjectionUtil.addSubEntry(subEntries1, parameter);
		parameters.getDetails().add(parameter);
		all.add(parameters);
		//对于表格我们分两个层次显示
		
		StepInjectionMetaEntry headers=new StepInjectionMetaEntry(Entry.HEADERS.name(),Entry.HEADERS.getValueType(),Entry.HEADERS.getDescription());
		StepInjectionMetaEntry header=new StepInjectionMetaEntry(Entry.HEADER.name(),Entry.HEADER.getValueType(),Entry.HEADER.getDescription());
		Entry[] subEntries2 = new Entry[] { Entry.HEADER_FIELD, Entry.HEADER_NAME };
		StepInjectionUtil.addSubEntry(subEntries2, header);
		headers.getDetails().add(header);
		all.add(headers);
		//对于表格我们分两个层次显示

		return all;
	}

3.4 实现injectStepMetadataEntries方法

injectStepMetadataEntries是对元数据注入的具体实现,在这个方法才是真正地改变元数据,这个方法接收一个List参数,对应着上一个方法的返回参数,但是该方法的合集参数中的第一个元素(元数据注入实体)已经包含了需要注入的具体值,而我们需要的是把这个值设置到RestMeta对象的对应属性中去,因此其实就是上一个方法的逆向解析过程:

@Override
	public void injectStepMetadataEntries(List metadata) throws KettleException {
		for (StepInjectionMetaEntry lookFields : metadata) {   //循环所有注入实体
			Entry lookEntry = Entry.findEntry(lookFields.getKey());
			if (lookEntry != null) {
				switch (lookEntry) {

				case PARAMETERS:  //如果是注入参数,由于参数是一个表格,一般对应RestMeta里的集合类型属性,注入逻辑比较多,所以单独实现到一个方法
					injectParameterFields(lookFields);
					break;
				case HEADERS:  //与注入参数一样
					injectHeaderFields(lookFields);
					break;
				case MOTHEDFIELD: //注入method,一些单一的参数,比较简单在RestMeta就是一个基本类型属性,下同
					meta.setMethod(String.valueOf(lookFields.getValue()));
					break;
				case BODY: //注入body
					meta.setBodyField(String.valueOf(lookFields.getValue()));
					break;
				case APPLICATIONTYPE:
					meta.setApplicationType(String.valueOf(lookFields.getValue()));
					break;
				case HTTPLOGIN:
					meta.setHttpLogin(String.valueOf(lookFields.getValue()));
					break;
				case HTTPPASSWORD:
					meta.setHttpPassword(String.valueOf(lookFields.getValue()));
					break;
				case PROXYHOST:
					meta.setProxyHost(String.valueOf(lookFields.getValue()));
					break;
				case PROXYPORT:
					meta.setProxyPort(String.valueOf(lookFields.getValue()));
					break;
				case TRUSTSTOREFILE:
					meta.setTrustStoreFile(String.valueOf(lookFields.getValue()));
					break;
				case TRUSTSTOREPASSWORD:
					meta.setTrustStorePassword(String.valueOf(lookFields.getValue()));
					break;
				default:
					break;
				}

			}
		}
	}

injectParameterFields实现代码如下:

/**
	 * 注入http参数
	 * 
	 * @param lookFields
	 */
	private void injectParameterFields(StepInjectionMetaEntry lookFields) {

		List paramFields = new ArrayList<>();
		List paramNames = new ArrayList<>();

		for (StepInjectionMetaEntry lookField : lookFields.getDetails()) {// 一行(参数字段、参数名称)
			Entry lookEntry = Entry.findEntry(lookField.getKey());
			if (lookEntry == Entry.PARAMETER) {
				for (StepInjectionMetaEntry lookDetail : lookField.getDetails()) { // 一列:参数字段或参数名称
					Entry detailEntry = Entry.findEntry(lookDetail.getKey());
					if (detailEntry != null) {
						String strValue = (String) lookDetail.getValue();
						switch (detailEntry) {
						case PARAMETER_FIELD:
							paramFields.add(strValue);
							break;
						case PARAMETER_NAME:
							paramNames.add(strValue);
							break;
						default:
							break;
						}
					}
				}
			}
		}

		if (paramFields.size() > 0 && paramNames.size() > 0 && paramFields.size() == paramNames.size()) {
			String[] paramFieldArr = new String[paramFields.size()];
			String[] paramNameArr = new String[paramNames.size()];
			meta.setParameterField(paramFields.toArray(paramFieldArr));
			meta.setParameterName(paramNames.toArray(paramNameArr));
		}

	}

injectHeaderFields实现代码如下:

/**
	 * 注入http请求头
	 * @param lookFields
	 */
	private void injectHeaderFields(StepInjectionMetaEntry lookFields) {
		List headFields = new ArrayList<>();
		List headNames = new ArrayList<>();

		for (StepInjectionMetaEntry lookField : lookFields.getDetails()) {// 一行(参数字段、参数名称)
			Entry lookEntry = Entry.findEntry(lookField.getKey());
			if (lookEntry == Entry.HEADER) {
				for (StepInjectionMetaEntry lookDetail : lookField.getDetails()) { // 一列:参数字段或参数名称
					Entry detailEntry = Entry.findEntry(lookDetail.getKey());
					if (detailEntry != null) {
						String strValue = (String) lookDetail.getValue();
						switch (detailEntry) {
						case HEADER_FIELD:
							headFields.add(strValue);
							break;
						case HEADER_NAME:
							headNames.add(strValue);
							break;
						default:
							break;
						}
					}
				}
			}
		}
		
		if (headFields.size() > 0 && headNames.size() > 0 && headFields.size() == headNames.size()) {
			String[] headFieldArr = new String[headFields.size()];
			String[] headNameArr = new String[headNames.size()];
			meta.setHeaderField(headFields.toArray(headFieldArr));
			meta.setHeaderName(headNames.toArray(headNameArr));
		}
	}

至此,RestMetaInjection类已经编写完毕。

3.5 修改RestMeta类

我们虽然实现了RestMetaInjection类,但还没与step关联起来,还虽然对RestMeta类稍作修改,重新实现一下getStepMetaInjectionInterface接口,代码如下:

	@Override
	  public StepMetaInjectionInterface getStepMetaInjectionInterface() {
		  return new RestMetaInjection(this);
	  }

4. 重新编译并使用

Rest Client组件的元数据注入已经实现,重新把源码编译,或者单独编译engin模块并更新对应的jar包,然后来验证一下扩展是否成功,随便创建两个转换,一个包含Rest Client插件,另一个包含ETL元数据注入步骤,最直观的方式就是在spoon界面中看下是否获取到Rest Client的注入属性:

kettle5.3扩展step插件支持元数据注入_第3张图片

经过测试验证,Rest Client通过元数据注入的方式可以正常运行,可以使用同样的方式来扩展我们一些常用的step。

你可能感兴趣的:(kettle,kettle,元数据注入)