網上關於Nifi自定義Processor的中文資料,要麼是很古老的eclipse版本,要麼太過於簡單,學習Nifi的道路確實有點看不清楚,好在找到一篇從零構造一個JsonPROCESSOR的英文文章,單純翻譯外加心得記錄,使用的Nifi版本是1.3.0,希望對大家有幫助。
源地址:http://www.nifi.rocks/developing-a-custom-apache-nifi-processor-json/
1. 開始
Nifi有很多可用的、文檔化的Processor資源,但是某些時候你依然需要去開發屬於你自己的Processor,例如從某些特殊的數據庫中提取數據、提取不常見的文件格式,或者其他特殊情況。這篇文章我們創建了一個基礎的json文件讀取Processor,將內容轉化爲屬性值,本片文章的代碼位於[GitHub](https://github.com/coco11563/jsonNifiExampleProcessor)
2. 項目依賴
目前搭建Nifi Processor項目環境使用較多的還是Maven + IDEA,與其他的組件開發不同,Nifi的要求是在/src/main/resources/META-INF/services/目錄下新建一個文件org.apache.nifi.processor.Processor,這個類似於配置文件,指向該Processor所在的目錄,比如我的配置文件內容就是
sha0w.pub.jsonNifi.processors.JsonProcessor
我的Maven POM文件的配置訊息如下所示,主要的幾個包是
- 提供nifi api的nifi-api
- 提供基礎工具的nifi-utils
- 提供Process抽象類接口的nifi-processor-utils
- 測試的nifi-mock以及junit
- jsonpath包
- common-io
附上POM文件詳細內容
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>sha0w.pub</groupId>
<artifactId>jsonNifi</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>nar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<nifi.version>1.3.0</nifi.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-nar-maven-plugin</artifactId>
<version>1.0.0-incubating</version>
<extensions>true</extensions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.15</version>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-api</artifactId>
<version>${nifi.version}</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-utils</artifactId>
<version>${nifi.version}</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-processor-utils</artifactId>
<version>${nifi.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-mock</artifactId>
<version>${nifi.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
POM內的plugin組件提供了一個將類打包成nifi組件的nar包打包方式(類似於war包),打包部分需要nifi-api依賴,其他組件在之後可以看到對應的作用。
3. JSON Processor
現在自定義Nifi Processor的前期準備工作都做完了,可以開始構建屬於我們自己的Processor了。首先我們在我們之前META-INF中的文件中寫入的地址處創建一個java class(比如說我的java class 就創建在sha0w.pub.jsonNifi.processors/下的 JsonProcessor.java)
@SideEffectFree
@Tags({"JSON","SHA0W.PUB"})
@CapabilityDescription("Fetch value from json path.")
public class JsonProcessor extends AbstractProcessor{
}
我們使其extends AbstractProcessor這個抽象類,@Tag標籤是爲了在web GUI中,能夠使用搜索的方式快速找到我們自己定義的這個Processor。CapabilityDescription內的值會暫時在Processor選擇的那個頁面中,相當於一個備註。
一般來說只需要繼承AbstractProcessor就可以了,但是某些複雜的任務可能需要去繼承更底層的AbstractSessionFactoryProcessor這個抽象類。
private List<PropertyDescriptor> properties;
private Set<Relationship> relationships;
public static final String MATCH_ATTR = "match";
public static final PropertyDescriptor JSON_PATH = new PropertyDescriptor.Builder()
.name("Json Path")
.required(true)
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
.build();
public static final Relationship SUCCESS = new Relationship.Builder()
.name("SUCCESS")
.description("Succes relationship")
.build();
以上代碼展示的是一些Processor內部變量以及一些初始化的Nifi變量,可見我們通過PropertyDescriptor
以及Relationship
中的模板方法定義了兩個新的關係和屬性描述值,這些值會出現在webUI中,多個選項型的屬性值定義如下:
public static final AllowableValue EXTENSIVE = new AllowableValue("Extensive", "Extensive",
"Everything will be logged - use with caution!");
public static final AllowableValue VERBOSE = new AllowableValue("Verbose", "Verbose",
"Quite a bit of logging will occur");
public static final AllowableValue REGULAR = new AllowableValue("Regular", "Regular",
"Typical logging will occur");
public static final PropertyDescriptor LOG_LEVEL = new PropertyDescriptor.Builder()
.name("Amount to Log")
.description("How much the Processor should log")
.allowableValues(REGULAR, VERBOSE, EXTENSIVE)
.defaultValue(REGULAR.getValue())
...
.build();
這樣定義的處理器的WEB UI的LOG_LEVEL選項內就會有三個選項。
@Override
public void init(final ProcessorInitializationContext context){
List<PropertyDescriptor> properties = new ArrayList<>();
properties.add(JSON_PATH);
this.properties = Collections.unmodifiableList(properties);
Set<Relationship> relationships = new HashSet<>();
relationships.add(SUCCESS);
this.relationships = Collections.unmodifiableSet(relationships);
}
@Override
public Set<Relationship> getRelationships(){
return relationships;
}
@Override
public List<PropertyDescriptor> getSupportedPropertyDescriptors(){
return properties;
}
如上是初始化Nifi進程,由於Nifi是高度併發條件,所以在此處進行更改時需要很小心,同時這也是爲什麼properties
和relationship
是存儲在一個不可變的集合中。
@Override
public void onTrigger(ProcessContext processContext, ProcessSession processSession) throws ProcessException {
final AtomicReference<String> value = new AtomicReference<>();
FlowFile flowFile = processSession.get();
processSession.read(flowFile, in -> {
try{
String json = IOUtils.toString(in);
String result = JsonPath.read(json, "$.hello");
value.set(result);
}catch(Exception ex){
ex.printStackTrace();
getLogger().error("Failed to read json string.");
}
});
String results = value.get();
if(results != null && !results.isEmpty()){
flowFile = processSession.putAttribute(flowFile, "match", results);
}
// To write the results back out ot flow file
flowFile = processSession.write(flowFile, out -> out.write(value.get().getBytes()));
processSession.transfer(flowFile, SUCCESS);
}
然後是整個Processor的核心部分 -> onTrigger 部分, onTrigger方法會在一個flow file被傳入處理器時調用。爲了讀取以及改變傳遞來的FlowFile,Nifi提供了三個callback接口方法:
- InputStreamCallback:
該接口繼承細節如下:
session.read(flowfile, new InputStreamCallback() {
@Override
public void process(InputStream in) throws IOException {
try{
String json = IOUtils.toString(in);
String result = JsonPath.read(json, "$.hello");
value.set(result);
}catch(Exception ex){
ex.printStackTrace();
getLogger().error("Failed to read json string.");
}
}
});
使用了Apache Commons內的方法去讀取輸入流,並將其轉換爲String形式,使用JsonPath包去讀取Json數據同時將值傳遞回來
- OutputStreamCallback
flowfile = session.write(flowfile, new OutputStreamCallback() {
@Override
public void process(OutputStream out) throws IOException {
out.write(value.get().getBytes());
}
});
可見這部分我們只是單純把之前的讀取內容寫到值中
- StreamCallback
flowfile = session.write(flowfile, new OutputStreamCallback() {
@Override
public void process(OutputStream out) throws IOException {
out.write(value.get().getBytes());
}
});
StreamCallback同時是同一個FlowFile的讀取以及寫入流
然後下面是將讀取json數據寫入到flowFile中。
// Write the results to an attribute
String results = value.get();
if(results != null && !results.isEmpty()){
flowfile = session.putAttribute(flowfile, "match", results);
}
最後使用transfer()
功能傳遞迴這個flowFile以及成功標識。
最後使用mvn clean install功能去build出這個nar包
4. 部署
- Copy the target/examples-1.0-SNAPSHOT.nar to $NIFI_HOME/lib
- $NIFI_HOME/bin/nifi.sh stop
- $NIFI_HOME/bin/nifi.sh start