You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@rocketmq.apache.org by ji...@apache.org on 2021/08/02 11:19:04 UTC
[rocketmq-streams] 02/27: add lease、dim and client module
This is an automated email from the ASF dual-hosted git repository.
jinrongtong pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/rocketmq-streams.git
commit b757f8db8141d282dd793d37af9b509bcc54b7ca
Author: 刈刀 <ju...@alibaba-inc.com>
AuthorDate: Mon Aug 2 11:21:45 2021 +0800
add lease、dim and client module
---
.gitignore | 25 ++
README.md | 108 +++++
pom.xml | 410 +++++++++++++++++++
rocketmq-streams-clients/pom.xml | 46 +++
.../rocketmq/streams/client/DataStreamAction.java | 101 +++++
.../rocketmq/streams/client/StreamBuilder.java | 28 ++
.../streams/client/source/DataStreamSource.java | 76 ++++
.../client/strategy/CheckpointStrategy.java | 69 ++++
.../streams/client/strategy/StateStrategy.java | 37 ++
.../rocketmq/streams/client/strategy/Strategy.java | 25 ++
.../streams/client/transform/DataStream.java | 437 +++++++++++++++++++++
.../streams/client/transform/JoinStream.java | 212 ++++++++++
.../streams/client/transform/SplitStream.java | 61 +++
.../streams/client/transform/WindowStream.java | 210 ++++++++++
.../client/transform/window/HoppingWindow.java | 32 ++
.../client/transform/window/SessionWindow.java | 32 ++
.../streams/client/transform/window/Time.java | 45 +++
.../client/transform/window/TumblingWindow.java | 33 ++
.../client/transform/window/WindowInfo.java | 83 ++++
.../rocketmq/streams/client/DBDriverTest.java | 75 ++++
.../rocketmq/streams/client/DataStreamTest.java | 107 +++++
.../apache/rocketmq/streams/client/FilterTest.java | 49 +++
.../apache/rocketmq/streams/client/JoinTest.java | 89 +++++
.../apache/rocketmq/streams/client/LeaseTest.java | 98 +++++
.../rocketmq/streams/client/ORMUtilTest.java | 172 ++++++++
.../apache/rocketmq/streams/client/SplitTest.java | 86 ++++
.../apache/rocketmq/streams/client/UnionTest.java | 82 ++++
.../apache/rocketmq/streams/client/WindowTest.java | 86 ++++
.../client/windows/AbstractWindowFireModeTest.java | 189 +++++++++
.../streams/client/windows/WindowFromFileTest.java | 158 ++++++++
.../streams/client/windows/WindowFromMetaq.java | 47 +++
.../client/windows/WindowHighAvailabilityTest.java | 131 ++++++
.../src/test/resources/log4j.xml | 36 ++
rocketmq-streams-dim/pom.xml | 47 +++
.../apache/rocketmq/streams/dim/DimComponent.java | 63 +++
.../rocketmq/streams/dim/builder/DimBuilder.java | 94 +++++
.../function/expression/InExpressionResource.java | 80 ++++
.../expression/NotInExpressionResource.java | 45 +++
.../dim/function/script/IntelligenceFunction.java | 81 ++++
.../script/IntelligenceNameListFunction.java | 24 ++
.../dim/function/script/NameListFunction.java | 203 ++++++++++
.../rocketmq/streams/dim/index/DimIndex.java | 319 +++++++++++++++
.../rocketmq/streams/dim/index/IndexExecutor.java | 258 ++++++++++++
.../intelligence/AbstractIntelligenceCache.java | 395 +++++++++++++++++++
.../dim/intelligence/AccountIntelligenceCache.java | 77 ++++
.../dim/intelligence/DomainIntelligenceCache.java | 83 ++++
.../dim/intelligence/IPIntelligenceCache.java | 108 +++++
.../dim/intelligence/URLIntelligenceCache.java | 80 ++++
.../rocketmq/streams/dim/model/AbstractDim.java | 312 +++++++++++++++
.../streams/dim/model/BooleanFieldDBDim.java | 55 +++
.../apache/rocketmq/streams/dim/model/DBDim.java | 140 +++++++
.../rocketmq/streams/dim/service/IDimService.java | 65 +++
.../streams/dim/service/impl/DimServiceImpl.java | 92 +++++
.../com/aliyun/service/ConfigureLoaderTest.java | 37 ++
.../com/aliyun/service/ExpressionExecutorTest.java | 80 ++++
.../java/com/aliyun/service/JsonParserTest.java | 40 ++
.../com/aliyun/service/NameListFunctionTest.java | 90 +++++
.../java/com/aliyun/service/TableCompressTest.java | 26 ++
rocketmq-streams-lease/pom.xml | 25 ++
.../rocketmq/streams/lease/LeaseComponent.java | 103 +++++
.../rocketmq/streams/lease/model/LeaseInfo.java | 127 ++++++
.../streams/lease/service/ILeaseGetCallback.java | 30 ++
.../streams/lease/service/ILeaseService.java | 136 +++++++
.../streams/lease/service/ILeaseStorage.java | 73 ++++
.../streams/lease/service/ILeaseStorasge.java | 63 +++
.../lease/service/impl/BasedLesaseImpl.java | 404 +++++++++++++++++++
.../lease/service/impl/LeaseServiceImpl.java | 275 +++++++++++++
.../streams/lease/service/impl/MockLeaseImpl.java | 95 +++++
.../lease/service/storages/DBLeaseStorage.java | 229 +++++++++++
.../rocketmq/streams/lease/LeaseComponentTest.java | 119 ++++++
.../src/test/resources/log4j.xml | 20 +
71 files changed, 8068 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1471653
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,25 @@
+target/
+.DS_Store
+!.mvn/wrapper/maven-wrapper.jar
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+nbproject/private/
+build/
+nbbuild/
+dist/
+nbdist/
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5f3cde5
--- /dev/null
+++ b/README.md
@@ -0,0 +1,108 @@
+# Rocketmq Streams
+
+## Features
+
+* 轻量级部署:可以单独部署,也支持集群部署
+* 多种类型的数据输入以及输出,source支持 rocketmq , sink支持db, rocketmq 等
+
+## DataStream Example
+
+```java
+import org.apache.rocketmq.streams.client.transform.DataStream;
+
+DataStreamSource source=StreamBuilder.dataStream("namespace","pipeline");
+
+ source
+ .fromFile("/Users/junjie.cheng/text.txt",false)
+ .map(message->message)
+ .toPrint(1)
+ .start();
+```
+
+## Maven Repository
+
+```xml
+
+<dependency>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-streams-clients</artifactId>
+ <version>2.0.0-SNAPSHOT</version>
+</dependency>
+```
+
+# Core API
+
+rocketmq-stream 实现了一系列高级的API,可以让用户很方便的编写流计算的程序,实现自己的业务需求;
+
+## StreamBuilder
+
+StreamBuilder 用于构建流任务的源; 内部包含```dataStream()```和```tableStream()```俩个方法,分别返回DataStreamSource和TableStreamSource俩个源;
+
++ [dataStream(nameSpaceName,pipelineName)]() 返回DataStreamSource实例,用于分段编程实现流计算任务;
+
+## DataStream API
+
+### Source
+
+DataStreamSource 是分段式编程的源头类,用于对接各种数据源, 从各大消息队列中获取数据;
+
++ ```fromFile``` 从文件中读取数据, 该方法包含俩个参数
+ + ```filePath``` 文件路径,必填参数
+ + ```isJsonData``` 是否json数据, 非必填参数, 默认为```true```
+
+
++ ```fromRocketmq``` 从rocketmq中获取数据,包含四个参数
+ + ```topic``` rocketmq消息队列的topic名称,必填参数
+ + ```groupName``` 消费者组的名称,必填参数
+ + ```isJson``` 是否json格式,非必填参数
+ + ```tags``` rocketmq消费的tags值,用于过滤消息,非必填参数
+
+
++ ```from``` 自定义的数据源, 通过实现ISource接口实现自己的数据源
+
+### transform
+
+transform 允许在流计算过程中对输入源的数据进行修改,进行下一步的操作;DataStream API中包括```DataStream```,```JoinStream```, ```SplitStream```,```WindowStream```等多个transform类;
+
+#### DataStream
+
+DataStream实现了一系列常见的流计算算子
+
++ ```map``` 通过将源的每个记录传递给函数func来返回一个新的DataStream
++ ```flatmap``` 与map类似,一个输入项对应0个或者多个输出项
++ ```filter``` 只选择func返回true的源DStream的记录来返回一个新的DStream
++ ```forEach``` 对每个记录执行一次函数func, 返回一个新的DataStream
++ ```selectFields``` 对每个记录返回对应的字段值,返回一个新的DataStream
++ ```operate``` 对每个记录执行一次自定义的函数,返回一个新的DataStream
++ ```script``` 针对每个记录的字段执行一段脚本,返回新的字段,生成一个新的DataStream
++ ```toPrint``` 将结果在控制台打印,生成新的DataStreamAction实例
++ ```toFile``` 将结果保存为文件,生成一个新的DataStreamAction实例
++ ```toDB``` 将结果保存到数据库
++ ```toRocketmq``` 将结果输出到rocketmq
++ ```toSls``` 将结果输出到sls
++ ```to``` 将结果经过自定义的ISink接口输出到指定的存储
++ ```window``` 在窗口内进行相关的统计分析,一般会与```groupBy```连用, ```window()```用来定义窗口的大小, ```groupBy()```用来定义统计分析的主key,可以指定多个
+ + ```count``` 在窗口内计数
+ + ```min``` 获取窗口内统计值的最小值
+ + ```max``` 获取窗口内统计值得最大值
+ + ```avg``` 获取窗口内统计值的平均值
+ + ```sum``` 获取窗口内统计值的加和值
+ + ```reduce``` 在窗口内进行自定义的汇总运算
++ ```join``` 根据条件将将俩个流进行关联, 合并为一个大流进行相关的运算
++ ```union``` 将俩个流进行合并
++ ```split``` 将一个数据流按照标签进行拆分,分为不同的数据流供下游进行分析计算
++ ```with``` with算子用来指定计算过程中的相关策略,包括checkpoint的存储策略,state的存储策略等
+
+# Strategy
+
+策略机制主要用来控制计算引擎运行过程中的底层逻辑,如checkpoint,state的存储方式等,后续还会增加对窗口、双流join等的控制;所有的控制策略通过```with```算子传入,可以同时传入多个策略类型;
+
+```java
+//指定checkpoint的存储策略
+source
+ .fromRocketmq("TSG_META_INFO","")
+ .map(message->message+"--")
+ .toPrint(1)
+ .with(CheckpointStrategy.db("jdbc:mysql://XXXXX:3306/XXXXX","","",0L))
+ .start();
+```
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..3675792
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,410 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<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>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-streams</artifactId>
+ <version>2.0.0-SNAPSHOT</version>
+ <name>ROCKETMQ STREAMS</name>
+ <packaging>pom</packaging>
+
+ <modules>
+ <module>rocketmq-streams-commons</module>
+ <module>rocketmq-streams-dim</module>
+ <module>rocketmq-streams-transport-minio</module>
+ <module>rocketmq-streams-script</module>
+ <module>rocketmq-streams-script-python</module>
+ <module>rocketmq-streams-configurable</module>
+ <module>rocketmq-streams-serviceloader</module>
+ <module>rocketmq-streams-filter</module>
+ <module>rocketmq-streams-schedule</module>
+ <module>rocketmq-streams-lease</module>
+ <module>rocketmq-streams-db-operator</module>
+ <module>rocketmq-streams-window</module>
+ <module>rocketmq-streams-clients</module>
+ <module>rocketmq-streams-channel-rocketmq</module>
+ <module>rocketmq-streams-channel-db</module>
+ <module>rocketmq-streams-channel-http</module>
+ </modules>
+
+ <properties>
+ <java.version>1.8</java.version>
+ <java.encoding>UTF-8</java.encoding>
+ <project.build.sourceEncoding>${java.encoding}</project.build.sourceEncoding>
+ <log4j.version>1.2.17</log4j.version>
+ <commons-logging.version>1.1</commons-logging.version>
+ <spring.version>3.2.13.RELEASE</spring.version>
+ <auto-service.version>1.0-rc5</auto-service.version>
+ <mysql-connector.version>5.1.40</mysql-connector.version>
+ <fastjson.version>1.2.27</fastjson.version>
+ <quartz.version>2.2.1</quartz.version>
+ <httpclient.version>4.5.2</httpclient.version>
+ <commons-io.version>2.5</commons-io.version>
+ <junit.version>4.12</junit.version>
+ <guava.version>25.1-jre</guava.version>
+ <groovy.version>2.1.8</groovy.version>
+ <disruptor.version>3.2.0</disruptor.version>
+ <rocksdbjni.version>6.6.4</rocksdbjni.version>
+ <rocketmq.version>4.5.2</rocketmq.version>
+ <hyperscan.version>5.4.0-2.0.0</hyperscan.version>
+ <platform.version>3.5.2</platform.version>
+ <gson.version>2.8.5</gson.version>
+ <java-grok.version>0.1.9</java-grok.version>
+ <jython.version>2.7.0</jython.version>
+ <scala-library.version>2.12.4</scala-library.version>
+ <logback-core.version>1.2.2</logback-core.version>
+ <minio.version>3.0.10</minio.version>
+ </properties>
+
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>2.18.1</version>
+ <configuration>
+ <skipTests>true</skipTests>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>2.5.1</version>
+ <configuration>
+ <source>${java.version}</source>
+ <target>${java.version}</target>
+ <encoding>UTF-8</encoding>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>versions-maven-plugin</artifactId>
+ <version>2.2</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <version>3.0.1</version>
+ <executions>
+ <execution>
+ <id>attach-sources</id>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencyManagement>
+ <dependencies>
+ <!-- ================================================= -->
+ <!-- rocketmq streams library -->
+ <!-- ================================================= -->
+ <dependency>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-streams-commons</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-streams-configurable</artifactId>
+ <version>${project.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-core</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-streams-db-operator</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-streams-dim</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-streams-filter</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-streams-lease</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-streams-schedule</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-streams-script</artifactId>
+ <version>${project.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-core</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-streams-script-python</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-streams-serviceloader</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-streams-transport-minio</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-streams-window</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-streams-channel-db</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-streams-channel-http</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-streams-channel-rocketmq</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <!-- ================================================= -->
+ <!-- rocketmq library -->
+ <!-- ================================================= -->
+
+ <dependency>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-tools</artifactId>
+ <version>${rocketmq.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-common</artifactId>
+ <version>${rocketmq.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-client</artifactId>
+ <version>${rocketmq.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-acl</artifactId>
+ <version>${rocketmq.version}</version>
+ </dependency>
+
+ <!-- ================================================= -->
+ <!-- tool library -->
+ <!-- ================================================= -->
+
+ <dependency>
+ <groupId>com.alibaba</groupId>
+ <artifactId>fastjson</artifactId>
+ <version>${fastjson.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ <version>${commons-logging.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>commons</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>${commons-io.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>${log4j.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.google.code.gson</groupId>
+ <artifactId>gson</artifactId>
+ <version>${gson.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>${guava.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.google.auto.service</groupId>
+ <artifactId>auto-service</artifactId>
+ <version>${auto-service.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <dependency>
+ <groupId>com.lmax</groupId>
+ <artifactId>disruptor</artifactId>
+ <version>${disruptor.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.gliwka.hyperscan</groupId>
+ <artifactId>hyperscan</artifactId>
+ <version>${hyperscan.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>net.java.dev.jna</groupId>
+ <artifactId>platform</artifactId>
+ <version>${platform.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-jdbc</artifactId>
+ <version>${spring.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>mysql</groupId>
+ <artifactId>mysql-connector-java</artifactId>
+ <version>${mysql-connector.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.quartz-scheduler</groupId>
+ <artifactId>quartz</artifactId>
+ <version>${quartz.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.quartz-scheduler</groupId>
+ <artifactId>quartz-jobs</artifactId>
+ <version>${quartz.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>io.krakens</groupId>
+ <artifactId>java-grok</artifactId>
+ <version>${java-grok.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.codehaus.groovy</groupId>
+ <artifactId>groovy-all</artifactId>
+ <version>${groovy.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.python</groupId>
+ <artifactId>jython-standalone</artifactId>
+ <version>${jython.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ <version>${httpclient.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpmime</artifactId>
+ <version>${httpclient.version}</version>
+ </dependency>
+
+
+ <dependency>
+ <groupId>org.scala-lang</groupId>
+ <artifactId>scala-library</artifactId>
+ <version>${scala-library.version}</version>
+ </dependency>
+
+
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-core</artifactId>
+ <version>${logback-core.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>io.minio</groupId>
+ <artifactId>minio</artifactId>
+ <version>${minio.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.rocksdb</groupId>
+ <artifactId>rocksdbjni</artifactId>
+ <version>${rocksdbjni.version}</version>
+ </dependency>
+
+ </dependencies>
+ </dependencyManagement>
+
+</project>
diff --git a/rocketmq-streams-clients/pom.xml b/rocketmq-streams-clients/pom.xml
new file mode 100644
index 0000000..ffc051a
--- /dev/null
+++ b/rocketmq-streams-clients/pom.xml
@@ -0,0 +1,46 @@
+<?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">
+ <parent>
+ <artifactId>rocketmq-streams</artifactId>
+ <groupId>org.apache.rocketmq</groupId>
+ <version>2.0.0-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>rocketmq-streams-clients</artifactId>
+ <name>ROCKETMQ STREAMS :: clients</name>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-streams-commons</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-streams-channel-rocketmq</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-streams-channel-db</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-streams-script</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-streams-filter</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-streams-window</artifactId>
+ </dependency>
+ </dependencies>
+
+ <properties>
+ <maven.compiler.source>8</maven.compiler.source>
+ <maven.compiler.target>8</maven.compiler.target>
+ </properties>
+
+</project>
\ No newline at end of file
diff --git a/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/DataStreamAction.java b/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/DataStreamAction.java
new file mode 100644
index 0000000..105bd8e
--- /dev/null
+++ b/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/DataStreamAction.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.streams.client;
+
+import com.google.common.collect.Maps;
+import org.apache.rocketmq.streams.client.strategy.Strategy;
+import org.apache.rocketmq.streams.client.transform.DataStream;
+import org.apache.rocketmq.streams.common.component.ComponentCreator;
+import org.apache.rocketmq.streams.common.configure.ConfigureFileKey;
+import org.apache.rocketmq.streams.common.topology.ChainPipeline;
+import org.apache.rocketmq.streams.common.topology.ChainStage;
+import org.apache.rocketmq.streams.common.topology.builder.PipelineBuilder;
+import org.apache.rocketmq.streams.configurable.ConfigurableComponent;
+
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+public class DataStreamAction extends DataStream {
+
+ private final Map<String, Object> properties = Maps.newHashMap();
+
+ public DataStreamAction(String namespace, String pipelineName) {
+ super(namespace, pipelineName);
+ }
+
+ public DataStreamAction(PipelineBuilder pipelineBuilder, Set<PipelineBuilder> pipelineBuilders, ChainStage<?> currentChainStage) {
+ super(pipelineBuilder, pipelineBuilders, currentChainStage);
+ }
+
+ public DataStreamAction with(Strategy... strategies) {
+ Properties properties = new Properties();
+ for (Strategy strategy : strategies) {
+ properties.putAll(strategy.getStrategyProperties());
+ }
+ ComponentCreator.createProperties(properties);
+ return this;
+ }
+
+ /**
+ * 启动流任务
+ */
+ public void start() {
+ start(false);
+ }
+
+ /**
+ * 启动流任务
+ */
+ public void asyncStart() {
+ start(true);
+ }
+
+ protected void start(boolean isAsync) {
+ if (this.mainPipelineBuilder == null) {
+ return;
+ }
+ properties.put(ConfigureFileKey.CONNECT_TYPE, "memory");
+ String[] kvs = new String[properties.size()];
+ int i = 0;
+ for (Map.Entry<String, Object> entry : properties.entrySet()) {
+ kvs[i++] = entry.getKey() + ":" + entry.getValue();
+ }
+
+ ConfigurableComponent configurableComponent = ComponentCreator.getComponent(mainPipelineBuilder.getPipelineNameSpace(), ConfigurableComponent.class, kvs);
+ ChainPipeline pipeline = this.mainPipelineBuilder.build(configurableComponent.getService());
+ pipeline.startChannel();
+ if (this.otherPipelineBuilders != null) {
+ for (PipelineBuilder builder : otherPipelineBuilders) {
+ ChainPipeline otherPipeline = builder.build(configurableComponent.getService());
+ otherPipeline.startChannel();
+ }
+ }
+ if (isAsync) {
+ return;
+ }
+ while (true) {
+ try {
+ Thread.sleep(10000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+}
diff --git a/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/StreamBuilder.java b/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/StreamBuilder.java
new file mode 100644
index 0000000..f67ee2c
--- /dev/null
+++ b/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/StreamBuilder.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.streams.client;
+
+import org.apache.rocketmq.streams.client.source.DataStreamSource;
+
+public class StreamBuilder {
+
+ public static DataStreamSource dataStream(String nameSpaceName, String pipelineName) {
+ return DataStreamSource.create(nameSpaceName, pipelineName);
+ }
+
+}
diff --git a/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/source/DataStreamSource.java b/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/source/DataStreamSource.java
new file mode 100644
index 0000000..8d71c31
--- /dev/null
+++ b/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/source/DataStreamSource.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.streams.client.source;
+
+import com.google.common.collect.Sets;
+import org.apache.rocketmq.streams.client.transform.DataStream;
+import org.apache.rocketmq.streams.common.channel.impl.file.FileSource;
+import org.apache.rocketmq.streams.common.channel.source.ISource;
+import org.apache.rocketmq.streams.common.topology.builder.PipelineBuilder;
+import org.apache.rocketmq.streams.source.RocketMQSource;
+
+import java.util.Set;
+
+public class DataStreamSource {
+ protected PipelineBuilder mainPipelineBuilder;
+ protected Set<PipelineBuilder> otherPipelineBuilders;
+
+ public DataStreamSource(String namespace, String pipelineName) {
+ this.mainPipelineBuilder = new PipelineBuilder(namespace, pipelineName);
+ this.otherPipelineBuilders = Sets.newHashSet();
+ }
+
+ public static DataStreamSource create(String namespace, String pipelineName) {
+ return new DataStreamSource(namespace, pipelineName);
+ }
+
+ public DataStream fromFile(String filePath) {
+ return fromFile(filePath, true);
+ }
+
+ public DataStream fromFile(String filePath, Boolean isJsonData) {
+ FileSource fileChannel = new FileSource(filePath);
+ fileChannel.setJsonData(isJsonData);
+ this.mainPipelineBuilder.setSource(fileChannel);
+ return new DataStream(this.mainPipelineBuilder, this.otherPipelineBuilders, null);
+ }
+
+ public DataStream fromRocketmq(String topic, String groupName) {
+ return fromRocketmq(topic, groupName, null, false);
+ }
+
+ public DataStream fromRocketmq(String topic, String groupName, boolean isJson) {
+ return fromRocketmq(topic, groupName, null, isJson);
+ }
+
+ public DataStream fromRocketmq(String topic, String groupName, String tags, boolean isJson) {
+ RocketMQSource rocketMQSource = new RocketMQSource();
+ rocketMQSource.setTopic(topic);
+ rocketMQSource.setTags(tags);
+ rocketMQSource.setGroupName(groupName);
+ rocketMQSource.setJsonData(isJson);
+ this.mainPipelineBuilder.setSource(rocketMQSource);
+ return new DataStream(this.mainPipelineBuilder, this.otherPipelineBuilders, null);
+ }
+
+ public DataStream from(ISource<?> source) {
+ this.mainPipelineBuilder.setSource(source);
+ return new DataStream(this.mainPipelineBuilder, this.otherPipelineBuilders, null);
+ }
+
+}
diff --git a/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/strategy/CheckpointStrategy.java b/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/strategy/CheckpointStrategy.java
new file mode 100644
index 0000000..20494c4
--- /dev/null
+++ b/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/strategy/CheckpointStrategy.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.client.strategy;
+
+import org.apache.rocketmq.streams.common.component.AbstractComponent;
+import org.apache.rocketmq.streams.common.configurable.IConfigurableService;
+
+import java.util.Properties;
+
+public class CheckpointStrategy implements Strategy {
+
+ private final Properties properties;
+
+ private CheckpointStrategy(Long pollingTime) {
+ properties = new Properties();
+ properties.put(AbstractComponent.CONNECT_TYPE, IConfigurableService.MEMORY_SERVICE_NAME);
+ properties.put(AbstractComponent.POLLING_TIME, pollingTime + "");
+ }
+
+ private CheckpointStrategy(String filePath, Long pollingTime) {
+ properties = new Properties();
+ properties.put(AbstractComponent.CONNECT_TYPE, IConfigurableService.FILE_SERVICE_NAME);
+ properties.put(IConfigurableService.FILE_PATH_NAME, filePath);
+ properties.put(AbstractComponent.POLLING_TIME, pollingTime + "");
+ }
+
+ private CheckpointStrategy(String url, String username, String password, Long pollingTime) {
+ properties = new Properties();
+ properties.put(AbstractComponent.JDBC_DRIVER, AbstractComponent.DEFAULT_JDBC_DRIVER);
+ properties.put(AbstractComponent.JDBC_URL, url);
+ properties.put(AbstractComponent.JDBC_USERNAME, username);
+ properties.put(AbstractComponent.JDBC_PASSWORD, password);
+ properties.put(AbstractComponent.JDBC_TABLE_NAME, AbstractComponent.DEFAULT_JDBC_TABLE_NAME);
+ properties.put(AbstractComponent.POLLING_TIME, pollingTime + "");
+ properties.put(AbstractComponent.CONNECT_TYPE, IConfigurableService.DEFAULT_SERVICE_NAME);
+ }
+
+ @Override
+ public Properties getStrategyProperties() {
+ return this.properties;
+ }
+
+ public static Strategy db(String url, String username, String password, Long pollingTime) {
+ return new CheckpointStrategy(url, username, password, pollingTime);
+ }
+
+ public static Strategy file(String filePath, Long pollingTime) {
+ return new CheckpointStrategy(filePath, pollingTime);
+ }
+
+ public static Strategy mem(Long pollingTime) {
+ return new CheckpointStrategy(pollingTime);
+ }
+
+}
diff --git a/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/strategy/StateStrategy.java b/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/strategy/StateStrategy.java
new file mode 100644
index 0000000..c647759
--- /dev/null
+++ b/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/strategy/StateStrategy.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.client.strategy;
+
+import java.util.Properties;
+
+public class StateStrategy implements Strategy {
+
+ private Properties properties;
+
+ private StateStrategy() {
+ }
+
+ @Override
+ public Properties getStrategyProperties() {
+ return this.properties;
+ }
+
+ public static Strategy db() {
+ return new StateStrategy();
+ }
+
+}
diff --git a/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/strategy/Strategy.java b/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/strategy/Strategy.java
new file mode 100644
index 0000000..727c645
--- /dev/null
+++ b/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/strategy/Strategy.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.client.strategy;
+
+import java.util.Properties;
+
+public interface Strategy {
+
+ Properties getStrategyProperties();
+
+}
diff --git a/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/transform/DataStream.java b/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/transform/DataStream.java
new file mode 100644
index 0000000..41aae96
--- /dev/null
+++ b/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/transform/DataStream.java
@@ -0,0 +1,437 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.streams.client.transform;
+
+import com.alibaba.fastjson.JSONObject;
+import com.google.common.collect.Sets;
+import org.apache.rocketmq.streams.client.DataStreamAction;
+import org.apache.rocketmq.streams.client.transform.window.WindowInfo;
+import org.apache.rocketmq.streams.common.channel.impl.OutputPrintChannel;
+import org.apache.rocketmq.streams.common.channel.impl.file.FileSink;
+import org.apache.rocketmq.streams.common.channel.sink.ISink;
+import org.apache.rocketmq.streams.common.component.ComponentCreator;
+import org.apache.rocketmq.streams.common.configure.ConfigureFileKey;
+import org.apache.rocketmq.streams.common.context.AbstractContext;
+import org.apache.rocketmq.streams.common.context.IMessage;
+import org.apache.rocketmq.streams.common.context.MessageHeader;
+import org.apache.rocketmq.streams.common.context.UserDefinedMessage;
+import org.apache.rocketmq.streams.common.functions.*;
+import org.apache.rocketmq.streams.common.topology.ChainPipeline;
+import org.apache.rocketmq.streams.common.topology.ChainStage;
+import org.apache.rocketmq.streams.common.topology.builder.IStageBuilder;
+import org.apache.rocketmq.streams.common.topology.builder.PipelineBuilder;
+import org.apache.rocketmq.streams.common.topology.model.Union;
+import org.apache.rocketmq.streams.common.topology.stages.udf.StageBuilder;
+import org.apache.rocketmq.streams.common.topology.stages.udf.UDFUnionChainStage;
+import org.apache.rocketmq.streams.common.utils.MapKeyUtil;
+import org.apache.rocketmq.streams.configurable.ConfigurableComponent;
+import org.apache.rocketmq.streams.db.sink.DBSink;
+import org.apache.rocketmq.streams.dim.model.DBDim;
+import org.apache.rocketmq.streams.filter.operator.FilterOperator;
+import org.apache.rocketmq.streams.script.operator.impl.ScriptOperator;
+import org.apache.rocketmq.streams.sink.RocketMQSink;
+import org.apache.rocketmq.streams.window.builder.WindowBuilder;
+import org.apache.rocketmq.streams.window.operator.AbstractWindow;
+import org.apache.rocketmq.streams.window.operator.join.JoinWindow;
+
+import java.io.Serializable;
+import java.util.Set;
+
+public class DataStream implements Serializable {
+
+ protected PipelineBuilder mainPipelineBuilder;
+ protected Set<PipelineBuilder> otherPipelineBuilders;
+ protected ChainStage<?> currentChainStage;
+
+ public DataStream(String namespace, String pipelineName) {
+ this.mainPipelineBuilder = new PipelineBuilder(namespace, pipelineName);
+ this.otherPipelineBuilders = Sets.newHashSet();
+ }
+
+ public DataStream(PipelineBuilder pipelineBuilder, Set<PipelineBuilder> pipelineBuilders, ChainStage<?> currentChainStage) {
+ this.mainPipelineBuilder = pipelineBuilder;
+ this.otherPipelineBuilders = pipelineBuilders;
+ this.currentChainStage = currentChainStage;
+ }
+
+ public DataStream script(String script) {
+ ChainStage<?> stage = this.mainPipelineBuilder.createStage(new ScriptOperator(script));
+ this.mainPipelineBuilder.setTopologyStages(currentChainStage, stage);
+ return new DataStream(this.mainPipelineBuilder, this.otherPipelineBuilders, stage);
+ }
+
+ public DataStream filter(String expressions) {
+ ChainStage<?> stage = this.mainPipelineBuilder.createStage(new FilterOperator(expressions));
+ this.mainPipelineBuilder.setTopologyStages(currentChainStage, stage);
+ return new DataStream(this.mainPipelineBuilder, this.otherPipelineBuilders, stage);
+ }
+
+ public <T, O> DataStream map(MapFunction<T, O> mapFunction) {
+ StageBuilder stageBuilder = new StageBuilder() {
+ @Override
+ protected <T> T operate(IMessage message, AbstractContext context) {
+ try {
+ O o = (O)(message.getMessageValue());
+ T result = (T)mapFunction.map(o);
+ if (result != message.getMessageValue()) {
+ if (result instanceof JSONObject) {
+ message.setMessageBody((JSONObject)result);
+ } else {
+ message.setMessageBody(new UserDefinedMessage(result));
+ }
+ }
+ return null;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+ };
+ ChainStage<?> stage = this.mainPipelineBuilder.createStage(stageBuilder);
+ this.mainPipelineBuilder.setTopologyStages(currentChainStage, stage);
+ return new DataStream(this.mainPipelineBuilder, this.otherPipelineBuilders, stage);
+ }
+
+ public <O> DataStream filter(final FilterFunction<O> filterFunction) {
+ StageBuilder mapUDFOperator = new StageBuilder() {
+
+ @Override
+ protected <T> T operate(IMessage message, AbstractContext context) {
+ try {
+ boolean isFilter = filterFunction.filter((O)message.getMessageValue());
+ if (isFilter) {
+ context.breakExecute();
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+ };
+ ChainStage stage = this.mainPipelineBuilder.createStage(mapUDFOperator);
+ this.mainPipelineBuilder.setTopologyStages(currentChainStage, stage);
+ return new DataStream(this.mainPipelineBuilder, this.otherPipelineBuilders, stage);
+ }
+
+ /**
+ * windows streams
+ *
+ * @param windowInfo 通过不同窗口类型动of方法创建,SessionWindow.of(Time.seconds(10))
+ * @return WindowStream
+ */
+ public WindowStream window(WindowInfo windowInfo) {
+ AbstractWindow window = windowInfo.createWindow();
+ ChainStage<?> stage = this.mainPipelineBuilder.createStage(window);
+ this.mainPipelineBuilder.setTopologyStages(currentChainStage, stage);
+ return new WindowStream(window, this.mainPipelineBuilder, this.otherPipelineBuilders, stage);
+ }
+
+ /**
+ * 通用增加stage的方法,低级接口,适合用户自定义stage的场景
+ *
+ * @param stageBuilder 创建stage和变量的接口
+ * @return DataStream
+ */
+ public DataStream addStage(IStageBuilder stageBuilder) {
+ ChainStage<?> stage = this.mainPipelineBuilder.createStage(stageBuilder);
+ this.mainPipelineBuilder.setTopologyStages(currentChainStage, stage);
+ return new DataStream(this.mainPipelineBuilder, this.otherPipelineBuilders, stage);
+ }
+
+ /**
+ * 创建join stream。实现原理,通过共享一个JoinWindow,并打标左右流,在join windown完成缓存,join逻辑
+ *
+ * @param rightStream 通过不同窗口类型动of方法创建,SessionWindow.of(Time.seconds(10))
+ * @return
+ */
+ public JoinStream join(DataStream rightStream) {
+ JoinWindow window = WindowBuilder.createDefaultJoinWindow();
+ //处理左边分支,增加map,主要是增加msg msgRouteFromLable->增加窗口stage
+ ChainStage<?> leftScriptStage = this.mainPipelineBuilder.createStage(new ScriptOperator("setHeader(msgRouteFromLable,'" + MessageHeader.JOIN_LEFT + "')"));
+ this.mainPipelineBuilder.setTopologyStages(currentChainStage, leftScriptStage);
+ this.currentChainStage = leftScriptStage;
+ ChainStage<?> leftWindowStage = this.mainPipelineBuilder.createStage(window);
+ this.mainPipelineBuilder.setTopologyStages(currentChainStage, leftWindowStage);
+
+ //处理右流,右流增加script
+ DataStream dataStream = rightStream.script("setHeader(msgRouteFromLable,'" + MessageHeader.JOIN_RIGHT + "')").addStage(window);
+ //dataStream.addStage(window);
+
+ addOtherDataStream(rightStream);
+ return new JoinStream(window, this.mainPipelineBuilder, this.otherPipelineBuilders, leftWindowStage);
+ }
+
+ /**
+ * 通过共享对象union,完成两个数据汇聚,左流需要设置isMainStream=true
+ *
+ * @param rightStream
+ * @return
+ */
+ public DataStream union(DataStream rightStream) {
+ Union union = new Union();
+
+ //处理左流,做流的isMain设置成true
+ UDFUnionChainStage chainStage = (UDFUnionChainStage)this.mainPipelineBuilder.createStage(union);
+ chainStage.setMainStream(true);
+ this.mainPipelineBuilder.setTopologyStages(currentChainStage, chainStage);
+
+ //处理右流,做流的isMain设置成true
+ rightStream.addStage(union);
+
+ addOtherDataStream(rightStream);
+ return new DataStream(this.mainPipelineBuilder, this.otherPipelineBuilders, chainStage);
+ }
+
+ /**
+ * 把一个流拆分成多个流,通过设置不同流的标签实现
+ *
+ * @param splitFunction 拆分流的具体逻辑
+ * @return
+ */
+ public SplitStream split(SplitFunction splitFunction) {
+ StageBuilder operator = new StageBuilder() {
+ @Override
+ protected <T> T operate(IMessage message, AbstractContext context) {
+ String labelName = splitFunction.split(message.getMessageValue());
+ message.getHeader().addRouteLable(labelName);
+ return null;
+ }
+ };
+ ChainStage<?> stage = this.mainPipelineBuilder.createStage(operator);
+ this.mainPipelineBuilder.setTopologyStages(currentChainStage, stage);
+ return new SplitStream(this.mainPipelineBuilder, this.otherPipelineBuilders, stage);
+ }
+
+ /**
+ * 维表join,mysql场景,不需要指定jdbcdriver
+ *
+ * @param url
+ * @param userName
+ * @param password
+ * @param sqlOrTableName
+ * @return
+ */
+ public JoinStream join(String url, String userName, String password, String sqlOrTableName, long pollingTimeMintue) {
+ return join(url, userName, password, sqlOrTableName, null, pollingTimeMintue);
+ }
+
+ /**
+ * 维表join
+ *
+ * @param url
+ * @param userName
+ * @param password
+ * @param sqlOrTableName
+ * @return
+ */
+ public JoinStream join(String url, String userName, String password, String sqlOrTableName, String jdbcDriver, long pollingTimeMinute) {
+ DBDim dbDim = new DBDim();
+ dbDim.setUrl(url);
+ dbDim.setUserName(userName);
+ dbDim.setPassword(password);
+ dbDim.setSql(sqlOrTableName);
+ dbDim.setPollingTimeMintue(pollingTimeMinute);
+ dbDim.setJdbcdriver(jdbcDriver);
+ this.mainPipelineBuilder.addConfigurables(dbDim);
+ return new JoinStream(dbDim, mainPipelineBuilder, otherPipelineBuilders, currentChainStage);
+ }
+
+ /**
+ * 遍历所有数据
+ *
+ * @param forEachFunction
+ * @param <O>
+ * @return
+ */
+ public <O> DataStream forEach(ForEachFunction<O> forEachFunction) {
+ StageBuilder selfChainStage = new StageBuilder() {
+ @Override
+ protected <T> T operate(IMessage message, AbstractContext context) {
+ forEachFunction.foreach((O)message.getMessageValue());
+ return null;
+ }
+ };
+ ChainStage stage = this.mainPipelineBuilder.createStage(selfChainStage);
+ this.mainPipelineBuilder.setTopologyStages(currentChainStage, stage);
+ return new DataStream(this.mainPipelineBuilder, this.otherPipelineBuilders, stage);
+ }
+
+ /**
+ * 遍历所有数据
+ *
+ * @param forEachFunction
+ * @param <O>
+ * @return
+ */
+ public <O> DataStream forEachMessage(ForEachMessageFunction forEachFunction) {
+ StageBuilder selfChainStage = new StageBuilder() {
+ @Override
+ protected <T> T operate(IMessage message, AbstractContext context) {
+ forEachFunction.foreach(message, context);
+ return null;
+ }
+ };
+ ChainStage stage = this.mainPipelineBuilder.createStage(selfChainStage);
+ this.mainPipelineBuilder.setTopologyStages(currentChainStage, stage);
+ return new DataStream(this.mainPipelineBuilder, this.otherPipelineBuilders, stage);
+ }
+
+ /**
+ * 只保留需要的字段
+ *
+ * @param fieldNames
+ */
+ public DataStream selectFields(String... fieldNames) {
+ ChainStage stage = this.mainPipelineBuilder.createStage(new ScriptOperator("retain(" + MapKeyUtil.createKeyBySign(",", fieldNames) + ")"));
+ this.mainPipelineBuilder.setTopologyStages(currentChainStage, stage);
+ return new DataStream(this.mainPipelineBuilder, this.otherPipelineBuilders, stage);
+ }
+
+ /**
+ * 启动流任务
+ */
+ public void start() {
+ start(false);
+ }
+
+ /**
+ * 启动流任务
+ */
+ public void asynStart() {
+ start(true);
+ }
+
+ protected void start(boolean isAsyn) {
+ if (this.mainPipelineBuilder == null) {
+ return;
+ }
+ ConfigurableComponent configurableComponent = ComponentCreator.getComponent(mainPipelineBuilder.getPipelineNameSpace(), ConfigurableComponent.class, ConfigureFileKey.CONNECT_TYPE + ":memory");
+ ChainPipeline pipeline = this.mainPipelineBuilder.build(configurableComponent.getService());
+ pipeline.startChannel();
+ if (this.otherPipelineBuilders != null) {
+ for (PipelineBuilder builder : otherPipelineBuilders) {
+ ChainPipeline otherPipeline = builder.build(configurableComponent.getService());
+ otherPipeline.startChannel();
+ }
+ }
+ if (isAsyn) {
+ return;
+ }
+ while (true) {
+ try {
+ Thread.sleep(10000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * 把其他流的 pipelinebuilder 放到set中
+ *
+ * @param rightSource
+ */
+ protected void addOtherDataStream(DataStream rightSource) {
+ //如果是多流join,需要把把piplinebuider保存下来,在启动时,启动多个pipline
+ if (!rightSource.mainPipelineBuilder.equals(this.mainPipelineBuilder)) {
+ this.otherPipelineBuilders.add(rightSource.mainPipelineBuilder);
+ }
+
+ this.otherPipelineBuilders.addAll(rightSource.otherPipelineBuilders);
+ }
+
+ public DataStreamAction toFile(String filePath) {
+ FileSink fileChannel = new FileSink(filePath);
+ ChainStage<?> output = mainPipelineBuilder.createStage(fileChannel);
+ mainPipelineBuilder.setTopologyStages(currentChainStage, output);
+ return new DataStreamAction(this.mainPipelineBuilder, this.otherPipelineBuilders, output);
+ }
+
+ public DataStreamAction toPrint() {
+ return toPrint(-1);
+ }
+
+ public DataStreamAction toPrint(int batchSize) {
+ OutputPrintChannel outputPrintChannel = new OutputPrintChannel();
+ if (batchSize > 0) {
+ outputPrintChannel.setBatchSize(batchSize);
+ }
+ ChainStage output = this.mainPipelineBuilder.createStage(outputPrintChannel);
+ this.mainPipelineBuilder.setTopologyStages(currentChainStage, output);
+ return new DataStreamAction(this.mainPipelineBuilder, this.otherPipelineBuilders, output);
+ }
+
+ public DataStreamAction toDB(String url, String userName, String password, String tableName) {
+ DBSink dbChannel = new DBSink(url, userName, password);
+ dbChannel.setTableName(tableName);
+ ChainStage<?> output = this.mainPipelineBuilder.createStage(dbChannel);
+ this.mainPipelineBuilder.setTopologyStages(currentChainStage, output);
+ return new DataStreamAction(this.mainPipelineBuilder, this.otherPipelineBuilders, output);
+ }
+
+ public DataStreamAction toMetaq(String topic) {
+ return toMetaq(topic, null);
+ }
+
+ public DataStreamAction toMetaq(String topic, String tags) {
+ return toMetaq(topic, tags, -1);
+ }
+
+ public DataStreamAction toMetaq(String topic, String tags, int batchSize) {
+ RocketMQSink metaqChannel = new RocketMQSink();
+ metaqChannel.setTopic(topic);
+ metaqChannel.setTags(tags);
+ if (batchSize > 0) {
+ metaqChannel.setBatchSize(batchSize);
+ }
+ ChainStage<?> output = this.mainPipelineBuilder.createStage(metaqChannel);
+ this.mainPipelineBuilder.setTopologyStages(currentChainStage, output);
+ return new DataStreamAction(this.mainPipelineBuilder, this.otherPipelineBuilders, output);
+ }
+
+ public DataStreamAction toRocketmq(String topic, String groupName, String endpoint, String namesrvAddr,
+ String accessKey, String secretKey, String instanceId) {
+ return toRocketmq(topic, "*", groupName, namesrvAddr, endpoint, accessKey, secretKey, instanceId);
+ }
+
+ public DataStreamAction toRocketmq(String topic, String tags, String groupName, String endpoint,
+ String namesrvAddr, String accessKey, String secretKey, String instanceId) {
+ return toRocketmq(topic, tags, -1, groupName, namesrvAddr, endpoint, accessKey, secretKey, instanceId);
+ }
+
+ public DataStreamAction toRocketmq(String topic, String tags, int batchSize, String groupName,
+ String endpoint, String namesrvAddr, String accessKey, String secretKey, String instanceId) {
+ RocketMQSink rocketMQSink = new RocketMQSink();
+ rocketMQSink.setTopic(topic);
+ rocketMQSink.setTags(tags);
+ rocketMQSink.setGroupName(groupName);
+ rocketMQSink.setNamesrvAddr(namesrvAddr);
+ if (batchSize > 0) {
+ rocketMQSink.setBatchSize(batchSize);
+ }
+ ChainStage<?> output = this.mainPipelineBuilder.createStage(rocketMQSink);
+ this.mainPipelineBuilder.setTopologyStages(currentChainStage, output);
+ return new DataStreamAction(this.mainPipelineBuilder, this.otherPipelineBuilders, output);
+ }
+
+ public DataStreamAction to(ISink<?> sink) {
+ ChainStage<?> output = this.mainPipelineBuilder.createStage(sink);
+ this.mainPipelineBuilder.setTopologyStages(currentChainStage, output);
+ return new DataStreamAction(this.mainPipelineBuilder, this.otherPipelineBuilders, output);
+ }
+}
diff --git a/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/transform/JoinStream.java b/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/transform/JoinStream.java
new file mode 100644
index 0000000..26e6420
--- /dev/null
+++ b/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/transform/JoinStream.java
@@ -0,0 +1,212 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.streams.client.transform;
+
+import org.apache.rocketmq.streams.client.transform.window.Time;
+import org.apache.rocketmq.streams.common.model.NameCreator;
+import org.apache.rocketmq.streams.common.topology.ChainStage;
+import org.apache.rocketmq.streams.common.topology.builder.PipelineBuilder;
+import org.apache.rocketmq.streams.dim.model.AbstractDim;
+import org.apache.rocketmq.streams.filter.builder.ExpressionBuilder;
+import org.apache.rocketmq.streams.filter.function.expression.Equals;
+import org.apache.rocketmq.streams.filter.operator.expression.Expression;
+import org.apache.rocketmq.streams.filter.operator.expression.RelationExpression;
+import org.apache.rocketmq.streams.script.operator.impl.ScriptOperator;
+import org.apache.rocketmq.streams.window.operator.join.JoinWindow;
+
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class JoinStream {
+
+ private static final String INNER_VAR_NAME_PREFIX = "___";
+ protected JoinWindow joinWindow;//完成join 条件的添加
+ protected boolean isDimJoin = false;//是否是维表join
+
+ protected AbstractDim dim;//维度表对象
+ protected String onCondition;//条件
+ protected JoinType joinType;//连接类型
+
+ //用于返回DataStream流
+ protected PipelineBuilder pipelineBuilder;
+ protected Set<PipelineBuilder> otherPipelineBuilders;
+ protected ChainStage<?> currentChainStage;
+
+ /**
+ * 双流join 场景
+ *
+ * @param joinWindow
+ * @param pipelineBuilder
+ * @param pipelineBuilders
+ * @param currentChainStage
+ */
+ public JoinStream(JoinWindow joinWindow, PipelineBuilder pipelineBuilder, Set<PipelineBuilder> pipelineBuilders, ChainStage<?> currentChainStage) {
+ this.pipelineBuilder = pipelineBuilder;
+ this.otherPipelineBuilders = pipelineBuilders;
+ this.currentChainStage = currentChainStage;
+ this.joinWindow = joinWindow;
+ }
+
+ /**
+ * 维表join 场景
+ *
+ * @param pipelineBuilder
+ * @param pipelineBuilders
+ * @param currentChainStage
+ */
+ public JoinStream(AbstractDim dim, PipelineBuilder pipelineBuilder, Set<PipelineBuilder> pipelineBuilders, ChainStage<?> currentChainStage) {
+ this.pipelineBuilder = pipelineBuilder;
+ this.otherPipelineBuilders = pipelineBuilders;
+ this.currentChainStage = currentChainStage;
+ this.dim = dim;
+ }
+
+ public JoinStream setJoinType(JoinType joinType) {
+ this.joinType = joinType;
+
+ return this;
+ }
+
+ /**
+ * 指定窗口,如果不指定,默认1个小时
+ *
+ * @param time
+ * @return
+ */
+ public JoinStream window(Time time) {
+
+ //维表join 不需要设置
+ if (isDimJoin) {
+ throw new RuntimeException("can not support this method");
+ }
+ joinWindow.setTimeUnitAdjust(1);
+ joinWindow.setSizeInterval(time.getValue());
+ joinWindow.setSlideInterval(time.getValue());
+ joinWindow.setRetainWindowCount(1);
+ return this;
+ }
+
+ /**
+ * 增加条件,用表达式形式表达(leftFieldName,function,rightFieldName)&&({name,==,otherName}||(age,==,age)) 后续再增加结构化的方法
+ *
+ * @param onCondition (leftFieldName,function,rightFieldName)&&({name,==,otherName}||(age,==,age))
+ * @return
+ */
+ public JoinStream setCondition(String onCondition) {
+ this.onCondition = onCondition;
+ return this;
+ }
+
+ public DataStream toDataSteam() {
+ if (isDimJoin) {
+ return doDimJoin();
+ } else {
+ return doJoin();
+ }
+
+ }
+
+ /**
+ * 维度表join的场景
+ */
+ protected DataStream doDimJoin() {
+ String script = null;
+ if (JoinType.INNER_JOIN == joinType) {
+ String data = createName("inner_join");
+ script = data + "=inner_join('" + dim.getNameSpace() + "','" + dim.getConfigureName() + "','" + onCondition + "'," + null + ",''," + null + ");splitArray('" + data + "');";
+ } else if ((JoinType.LEFT_JOIN == joinType)) {
+ String data = createName("left_join");
+ script = data + "=left_join('" + dim.getNameSpace() + "','" + dim.getConfigureName() + "','" + onCondition + "'," + null + ",'" + null + "'," + null + ");if(!null(" + data + ")){splitArray('" + data + "');};";
+ }
+ ChainStage stage = this.pipelineBuilder.createStage(new ScriptOperator(script));
+ this.pipelineBuilder.setTopologyStages(currentChainStage, stage);
+ return new DataStream(pipelineBuilder, otherPipelineBuilders, stage);
+ }
+
+ /**
+ * 双流join的场景
+ */
+ protected DataStream doJoin() {
+ if (JoinType.INNER_JOIN == joinType) {
+ joinWindow.setJoinType("INNER");
+ } else if (JoinType.LEFT_JOIN == joinType) {
+ joinWindow.setJoinType("LEFT");
+ } else {
+ throw new RuntimeException("can not support this join type, expect INNER,LEFT, real is " + joinType.toString());
+ }
+
+ AtomicBoolean hasNoEqualsExpression = new AtomicBoolean(false);//是否有非等值的join 条件
+ Map<String, String> left2Right = createJoinFieldsFromCondition(onCondition, hasNoEqualsExpression);//把等值条件的左右字段映射成map
+ List<String> leftList = new ArrayList<>();
+ List<String> rightList = new ArrayList<>();
+ leftList.addAll(left2Right.keySet());
+ rightList.addAll(left2Right.values());
+ joinWindow.setLeftJoinFieldNames(leftList);
+ joinWindow.setRightJoinFieldNames(rightList);
+ //如果有非等值,则把这个条件设置进去
+ if (hasNoEqualsExpression.get()) {
+ joinWindow.setExpression(onCondition);
+ }
+ return new DataStream(pipelineBuilder, otherPipelineBuilders, currentChainStage);
+ }
+
+ /**
+ * 支持的连接类型,目前支持inner join和left join
+ */
+ public enum JoinType {
+ INNER_JOIN,
+ LEFT_JOIN
+ }
+
+ /**
+ * 从条件中找到join 左右的字段。如果有非等值,则不包含在内
+ *
+ * @param
+ * @param onCondition
+ * @return
+ */
+ public Map<String, String> createJoinFieldsFromCondition(String onCondition, AtomicBoolean hasNoEqualsExpression) {
+ List<Expression> expressions = new ArrayList<>();
+ List<RelationExpression> relationExpressions = new ArrayList<>();
+ ExpressionBuilder.createOptimizationExpression("tmp", "tmp", onCondition, expressions, relationExpressions);
+ Map<String, String> left2Right = new HashMap<>();
+ for (Expression expression : expressions) {
+ String varName = expression.getVarName();
+ String valueName = expression.getValue().toString();
+ if (!Equals.isEqualFunction(expression.getFunctionName())) {
+ hasNoEqualsExpression.set(true);
+ continue;
+ }
+ left2Right.put(varName, valueName);
+ }
+ return left2Right;
+ }
+
+ public static String createName(String functionName, String... names) {
+ if (names == null || names.length == 0) {
+ return NameCreator.createNewName(INNER_VAR_NAME_PREFIX, functionName);
+ }
+ String[] values = new String[names.length + 2];
+ values[0] = INNER_VAR_NAME_PREFIX;
+ values[1] = functionName;
+ for (int i = 2; i < values.length; i++) {
+ values[i] = names[i - 2];
+ }
+ return NameCreator.createNewName(values);
+ }
+}
diff --git a/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/transform/SplitStream.java b/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/transform/SplitStream.java
new file mode 100644
index 0000000..49efcd2
--- /dev/null
+++ b/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/transform/SplitStream.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.streams.client.transform;
+
+import org.apache.rocketmq.streams.common.context.AbstractContext;
+import org.apache.rocketmq.streams.common.context.IMessage;
+import org.apache.rocketmq.streams.common.topology.ChainStage;
+import org.apache.rocketmq.streams.common.topology.builder.PipelineBuilder;
+import org.apache.rocketmq.streams.common.topology.stages.udf.StageBuilder;
+
+import java.util.Set;
+
+public class SplitStream {
+
+
+ /**
+ * 创建datastream时使用
+ */
+ protected PipelineBuilder pipelineBuilder;
+ protected Set<PipelineBuilder> otherPipelineBuilders;
+ protected ChainStage<?> currentChainStage;
+
+
+ public SplitStream(PipelineBuilder pipelineBuilder, Set<PipelineBuilder> pipelineBuilders, ChainStage<?> currentChainStage) {
+ this.pipelineBuilder = pipelineBuilder;
+ this.otherPipelineBuilders = pipelineBuilders;
+ this.currentChainStage = currentChainStage;
+ }
+
+ /**
+ * 选择一个分支
+ * @param lableName
+ * @return
+ */
+ public DataStream select(String lableName){
+ StageBuilder stage = new StageBuilder() {
+ @Override
+ protected <T> T operate(IMessage message, AbstractContext context) {
+ return null;
+ }
+ };
+ stage.setLabel(lableName);
+ this.pipelineBuilder.setTopologyStages(currentChainStage,stage);
+ return new DataStream(pipelineBuilder,otherPipelineBuilders,stage);
+ }
+}
diff --git a/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/transform/WindowStream.java b/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/transform/WindowStream.java
new file mode 100644
index 0000000..1f6ea24
--- /dev/null
+++ b/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/transform/WindowStream.java
@@ -0,0 +1,210 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.streams.client.transform;
+
+import com.alibaba.fastjson.JSONObject;
+import org.apache.rocketmq.streams.common.context.UserDefinedMessage;
+import org.apache.rocketmq.streams.common.functions.ReduceFunction;
+import org.apache.rocketmq.streams.common.topology.ChainStage;
+import org.apache.rocketmq.streams.common.topology.builder.PipelineBuilder;
+import org.apache.rocketmq.streams.common.topology.stages.udf.IReducer;
+import org.apache.rocketmq.streams.common.utils.MapKeyUtil;
+import org.apache.rocketmq.streams.window.operator.AbstractWindow;
+
+import java.util.Set;
+
+/**
+ * 做windown 相关操作 可以同时设置多个统计算子,如count,sum,avg 通过toDataSteam/reduce 返回DataSteam
+ */
+public class WindowStream {
+ //window 对象
+ protected AbstractWindow window;
+
+ /**
+ * 创建datastream时使用
+ */
+ protected PipelineBuilder pipelineBuilder;
+ protected Set<PipelineBuilder> otherPipelineBuilders;
+ protected ChainStage<?> currentChainStage;
+
+ public WindowStream(AbstractWindow window, PipelineBuilder pipelineBuilder, Set<PipelineBuilder> pipelineBuilders, ChainStage<?> currentChainStage) {
+ this.pipelineBuilder = pipelineBuilder;
+ this.otherPipelineBuilders = pipelineBuilders;
+ this.currentChainStage = currentChainStage;
+ this.window = window;
+ }
+
+ /**
+ * 做count算子
+ *
+ * @param asName count结果对应的名字,如 sql中count(1) as c 。asName=c
+ * @return
+ */
+ public WindowStream count(String asName) {
+ window.getSelectMap().put(asName, asName + "=count(" + asName + ")");
+ return this;
+ }
+
+ /**
+ * 做min算子
+ *
+ * @param fieldName 算子需要操作的字段名
+ * @return
+ */
+ public WindowStream min(String fieldName) {
+ window.getSelectMap().put(fieldName, fieldName + "=min(" + fieldName + ")");
+ return this;
+ }
+
+ /**
+ * 做max算子
+ *
+ * @param fieldName 算子需要操作的字段名
+ * @return
+ */
+ public WindowStream max(String fieldName) {
+ window.getSelectMap().put(fieldName, fieldName + "=max(" + fieldName + ")");
+ return this;
+ }
+
+ /**
+ * 做avg算子
+ *
+ * @param fieldName 算子需要操作的字段名
+ * @param asName avg结果对应的名字,如 sql中avg(name) as c 。asName=c
+ * @return
+ */
+ public WindowStream avg(String fieldName, String asName) {
+ window.getSelectMap().put(asName, asName + "=avg(" + fieldName + ")");
+ return this;
+ }
+
+ /**
+ * 做sum算子
+ *
+ * @param fieldName 算子需要操作的字段名
+ * @param asName sum结果对应的名字,如 sql中sum(name) as c 。asName=c
+ * @return
+ */
+ public WindowStream sum(String fieldName, String asName) {
+ window.getSelectMap().put(asName, asName + "=sum(" + fieldName + ")");
+ return this;
+ }
+
+ public WindowStream setTimeField(String timeField) {
+ window.setTimeFieldName(timeField);
+ return this;
+ }
+
+ public WindowStream setFireMode(int fireMode) {
+ window.setFireMode(fireMode);
+ return this;
+ }
+
+ public WindowStream setLocalStorageOnly(boolean isLocalStorageOnley) {
+ window.setLocalStorageOnly(isLocalStorageOnley);
+ return this;
+ }
+
+ public WindowStream setMaxMsgGap(Long maxMsgGapSecond) {
+ window.setMsgMaxGapSecond(maxMsgGapSecond);
+ return this;
+ }
+
+ /**
+ * 以哪几个字段做分组,支持多个字段
+ *
+ * @param fieldNames
+ * @return
+ */
+ public WindowStream groupBy(String... fieldNames) {
+ window.setGroupByFieldName(MapKeyUtil.createKeyBySign(";", fieldNames));
+ for (String fieldName : fieldNames) {
+ window.getSelectMap().put(fieldName, fieldName);
+ }
+
+ return this;
+ }
+
+ /**
+ * 以哪几个字段做分组,支持多个字段
+ *
+ * @param fireMode
+ * @return
+ */
+ public WindowStream fireMode(int fireMode) {
+ window.setFireMode(fireMode);
+
+ return this;
+ }
+
+ /**
+ * 以哪几个字段做分组,支持多个字段
+ *
+ * @param waterMarkSecond
+ * @return
+ */
+ public WindowStream waterMark(int waterMarkSecond) {
+ window.setWaterMarkMinute(waterMarkSecond);
+
+ return this;
+ }
+
+ /**
+ * 以哪几个字段做分组,支持多个字段
+ *
+ * @param fieldName
+ * @return
+ */
+ public WindowStream timeField(String fieldName) {
+ window.setTimeFieldName(fieldName);
+
+ return this;
+ }
+
+ /**
+ * 用户自定义reduce逻辑
+ *
+ * @param reduceFunction
+ * @return
+ */
+ public <R, O> DataStream reduce(ReduceFunction<R, O> reduceFunction) {
+ window.setReducer(new IReducer() {
+ @Override
+ public JSONObject reduce(JSONObject accumulator, JSONObject msg) {
+ Object accumulatorValue = accumulator;
+ Object msgValue = msg;
+ if (msg instanceof UserDefinedMessage) {
+ UserDefinedMessage userDefinedMessage = (UserDefinedMessage)msg;
+ msgValue = userDefinedMessage.getMessageValue();
+ }
+ if (accumulator instanceof UserDefinedMessage) {
+ UserDefinedMessage userDefinedMessage = (UserDefinedMessage)accumulator;
+ accumulatorValue = userDefinedMessage.getMessageValue();
+ }
+ R result = reduceFunction.reduce((R)accumulatorValue, (O)msgValue);
+ return new UserDefinedMessage(result);
+ }
+ });
+ return new DataStream(pipelineBuilder, otherPipelineBuilders, currentChainStage);
+ }
+
+ public DataStream toDataSteam() {
+ return new DataStream(pipelineBuilder, otherPipelineBuilders, currentChainStage);
+ }
+}
diff --git a/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/transform/window/HoppingWindow.java b/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/transform/window/HoppingWindow.java
new file mode 100644
index 0000000..0c945f0
--- /dev/null
+++ b/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/transform/window/HoppingWindow.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.streams.client.transform.window;
+
+public class HoppingWindow {
+ /**
+ * 滑动窗口信息
+ * @return
+ */
+ public static WindowInfo of(Time windowSize,Time windowSlide){
+ WindowInfo windowInfo=new WindowInfo();
+ windowInfo.setType(WindowInfo.HOPPING_WINDOW);
+ windowInfo.setWindowSize(windowSize);
+ windowInfo.setWindowSlide(windowSlide);
+ return windowInfo;
+ }
+}
diff --git a/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/transform/window/SessionWindow.java b/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/transform/window/SessionWindow.java
new file mode 100644
index 0000000..67c7f22
--- /dev/null
+++ b/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/transform/window/SessionWindow.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.streams.client.transform.window;
+
+public class SessionWindow {
+ /**
+ * 滑动窗口信息
+ * @param time
+ * @return
+ */
+ public static WindowInfo of(Time time){
+ WindowInfo windowInfo=new WindowInfo();
+ windowInfo.setType(WindowInfo.SESSION_WINDOW);
+ windowInfo.setWindowSize(time);
+ return windowInfo;
+ }
+}
diff --git a/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/transform/window/Time.java b/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/transform/window/Time.java
new file mode 100644
index 0000000..2157e01
--- /dev/null
+++ b/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/transform/window/Time.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.streams.client.transform.window;
+
+/**
+ * 给window指定窗口时间
+ */
+public class Time {
+ protected int value;
+ public Time(int value){
+ this.value=value;
+ }
+ public static Time seconds(int second){
+
+ return new Time(second);
+ }
+ public static Time minutes(int minutes){
+ return new Time(minutes*60);
+ }
+ public static Time hours(int hours){
+ return new Time(60*60*hours);
+ }
+ public static Time days(int days){
+ return new Time(60*60*24*days);
+ }
+
+ public int getValue() {
+ return value;
+ }
+}
diff --git a/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/transform/window/TumblingWindow.java b/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/transform/window/TumblingWindow.java
new file mode 100644
index 0000000..62c0ce3
--- /dev/null
+++ b/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/transform/window/TumblingWindow.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.streams.client.transform.window;
+
+public class TumblingWindow {
+ /**
+ * 滑动窗口信息
+ *
+ * @param time
+ * @return
+ */
+ public static WindowInfo of(Time time) {
+ WindowInfo windowInfo = new WindowInfo();
+ windowInfo.setType(WindowInfo.TUMBLING_WINDOW);
+ windowInfo.setWindowSize(time);
+ return windowInfo;
+ }
+}
diff --git a/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/transform/window/WindowInfo.java b/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/transform/window/WindowInfo.java
new file mode 100644
index 0000000..64be503
--- /dev/null
+++ b/rocketmq-streams-clients/src/main/java/org/apache/rocketmq/streams/client/transform/window/WindowInfo.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.client.transform.window;
+
+import org.apache.rocketmq.streams.window.operator.AbstractWindow;
+import org.apache.rocketmq.streams.window.operator.impl.SessionWindow;
+import org.apache.rocketmq.streams.window.operator.impl.WindowOperator;
+
+/**
+ * 保存创建window的信息 主要是窗口类型,窗口大小
+ */
+public class WindowInfo {
+ public static int HOPPING_WINDOW = 1;//滑动窗口
+ public static int TUMBLING_WINDOW = 2;//滚动窗口
+ public static int SESSION_WINDOW = 23;
+ protected int type;//window类型 hopping,Tumbling
+ protected Time windowSize;//窗口大小
+ protected Time windowSlide;//滑动大小
+
+ /**
+ * 创建窗口
+ *
+ * @return
+ */
+ public AbstractWindow createWindow() {
+ AbstractWindow window = null;
+ if (type == HOPPING_WINDOW) {
+ window = new WindowOperator();
+ window.setTimeUnitAdjust(1);
+ window.setSizeInterval(windowSize.getValue());
+ window.setSlideInterval(windowSlide.getValue());
+ } else if (type == TUMBLING_WINDOW) {
+ window = new WindowOperator();
+ window.setTimeUnitAdjust(1);
+ window.setSizeInterval(windowSize.getValue());
+ } else if (type == SESSION_WINDOW) {
+ window = new SessionWindow();
+ window.setTimeUnitAdjust(1);
+ window.setSizeInterval(windowSize.getValue());
+ } else {
+ throw new RuntimeException("can not support the type ,expect 1,2,3。actual is " + type);
+ }
+ return window;
+ }
+
+ public int getType() {
+ return type;
+ }
+
+ public void setType(int type) {
+ this.type = type;
+ }
+
+ public Time getWindowSize() {
+ return windowSize;
+ }
+
+ public void setWindowSize(Time windowSize) {
+ this.windowSize = windowSize;
+ }
+
+ public Time getWindowSlide() {
+ return windowSlide;
+ }
+
+ public void setWindowSlide(Time windowSlide) {
+ this.windowSlide = windowSlide;
+ }
+}
diff --git a/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/DBDriverTest.java b/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/DBDriverTest.java
new file mode 100644
index 0000000..7e078f8
--- /dev/null
+++ b/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/DBDriverTest.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.streams.client;
+
+import org.apache.rocketmq.streams.common.component.ComponentCreator;
+import org.apache.rocketmq.streams.common.configure.ConfigureFileKey;
+import org.apache.rocketmq.streams.configuable.ConfigurableComponent;
+import org.apache.rocketmq.streams.configuable.model.Configure;
+import org.apache.rocketmq.streams.db.driver.DriverBuilder;
+import org.junit.Test;
+
+import static junit.framework.TestCase.assertNotNull;
+import static junit.framework.TestCase.assertTrue;
+
+/**
+ * 数据库的存储,需要配置存储的连接参数,请先完成配置,后执行单元用例 如果未建表,可以通过Configure.createTableSQL() 获取建表语句,创建表后,测试
+ */
+public class DBDriverTest {
+ private String URL = "";
+ protected String USER_NAME = "";
+ protected String PASSWORD = "";
+ protected String TABLE_NAME = "rocketmq_streams_configure_source";
+
+ @Test
+ public void testDBConfigurableService() {
+ String namespace = "streams.db.configurable";
+
+ //正式使用时,在配置文件配置
+ ComponentCreator.getProperties().put(ConfigureFileKey.CONNECT_TYPE, "DB");
+ ComponentCreator.getProperties().put(ConfigureFileKey.JDBC_URL, URL);//数据库连接url
+ ComponentCreator.getProperties().put(ConfigureFileKey.JDBC_USERNAME, USER_NAME);//用户名
+ ComponentCreator.getProperties().put(ConfigureFileKey.JDBC_PASSWORD, PASSWORD);//password
+ ComponentCreator.getProperties().put(ConfigureFileKey.JDBC_TABLE_NAME, TABLE_NAME);
+
+ //如果表不存在,创建表
+ String sql = (Configure.createTableSQL(TABLE_NAME));
+ DriverBuilder.createDriver().execute(sql);
+ ConfigurableComponent configurableComponent = ConfigurableComponent.getInstance(namespace);
+ configurableComponent.insert(createPerson(namespace));
+ configurableComponent.refreshConfigurable(namespace);
+ Person person = configurableComponent.queryConfigurable("person", "peronName");
+ assertNotNull(person);
+ }
+
+ /**
+ * 创建configuable对象
+ *
+ * @param namespace
+ * @return
+ */
+ protected Person createPerson(String namespace) {
+ Person person = new Person();
+ person.setName("chris");
+ person.setAge(18);
+ person.setNameSpace(namespace);
+ person.setConfigureName("peronName");
+ person.setType("person");
+ return person;
+ }
+}
diff --git a/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/DataStreamTest.java b/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/DataStreamTest.java
new file mode 100644
index 0000000..8f54067
--- /dev/null
+++ b/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/DataStreamTest.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.streams.client;
+
+import org.apache.rocketmq.streams.client.source.DataStreamSource;
+import org.apache.rocketmq.streams.client.strategy.CheckpointStrategy;
+import org.apache.rocketmq.streams.client.strategy.StateStrategy;
+import org.apache.rocketmq.streams.common.utils.DataTypeUtil;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.Serializable;
+import java.sql.*;
+
+public class DataStreamTest implements Serializable {
+
+ DataStreamSource dataStream;
+
+ @Before
+ public void init() {
+ dataStream = StreamBuilder.dataStream("test_namespace", "graph_pipeline");
+ }
+
+ @Test
+ public void testFromFile() {
+ dataStream
+ .fromFile("/Users/junjie.cheng/text.txt", false)
+ .map(message -> message + "--")
+ .toPrint(1)
+ .start();
+ }
+
+ @Test
+ public void testRocketmq() {
+ dataStream
+ .fromRocketmq("TOPIC_EVENT_SAS_SECURITY_EVENT", "111")
+ .map(message -> message + "--")
+ .toPrint(1)
+ .start();
+ }
+
+ @Test
+ public void testDBCheckPoint() {
+ dataStream
+ .fromRocketmq("TSG_META_INFO", "")
+ .map(message -> message + "--")
+ .toPrint(1)
+ .with(CheckpointStrategy.db("", "", "", 0L))
+ .start();
+ }
+
+ @Test
+ public void testFileCheckPoint() {
+ dataStream
+ .fromRocketmq("TSG_META_INFO", "")
+ .map(message -> message + "--")
+ .toPrint(1)
+ .with(CheckpointStrategy.mem(0L))
+ .start();
+ }
+
+ @Test
+ public void testBothStrategy() {
+ dataStream
+ .fromRocketmq("TSG_META_INFO", "")
+ .map(message -> message + "--")
+ .toPrint(1)
+ .with(CheckpointStrategy.mem(0L), StateStrategy.db())
+ .start();
+ }
+
+ @Test
+ public void testMeta() {
+ try {
+ Class.forName("com.mysql.jdbc.Driver");
+ Connection connection = DriverManager.getConnection("", "", "");
+ DatabaseMetaData metaData = connection.getMetaData();
+ ResultSet dataFilter = metaData.getColumns(connection.getCatalog(), "%", "XXX", null);
+ while (dataFilter.next()) {
+ String one = dataFilter.getString("DATA_TYPE");
+ int two = dataFilter.getInt("COLUMN_SIZE");
+ String three = dataFilter.getString("COLUMN_NAME");
+ String four = dataFilter.getString("TYPE_NAME");
+ System.out.println(one + " " + two + " " + three + " " + four + " " + DataTypeUtil.getDataType(dataFilter.getString("TYPE_NAME")));
+ }
+
+ } catch (ClassNotFoundException | SQLException e) {
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/FilterTest.java b/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/FilterTest.java
new file mode 100644
index 0000000..19665fd
--- /dev/null
+++ b/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/FilterTest.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.streams.client;
+
+import com.alibaba.fastjson.JSONObject;
+import java.util.List;
+import org.apache.rocketmq.streams.filter.FilterComponent;
+import org.apache.rocketmq.streams.filter.builder.ExpressionBuilder;
+import org.apache.rocketmq.streams.filter.operator.Rule;
+import org.junit.Test;
+
+import static junit.framework.TestCase.assertTrue;
+
+public class FilterTest {
+ @Test
+ public void testFilter(){
+ JSONObject msg=new JSONObject();
+ msg.put("name","chris");
+ msg.put("age",18);
+ Rule rule= ExpressionBuilder.createRule("tmp","tmp","(name,==,chris)&(age,>=,18)");
+ FilterComponent filterComponent=FilterComponent.getInstance();
+ List<Rule> fireRules=filterComponent.excuteRule(msg,rule);
+ assertTrue(fireRules.size()==1);
+ }
+
+ @Test
+ public void testFilter2(){
+ JSONObject msg=new JSONObject();
+ msg.put("name","chris");
+ msg.put("age",18);
+ boolean result= ExpressionBuilder.executeExecute("tmp","(name,==,chris)&(age,>,int,18)",msg);
+ assertTrue(!result);
+ }
+}
diff --git a/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/JoinTest.java b/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/JoinTest.java
new file mode 100644
index 0000000..b221e92
--- /dev/null
+++ b/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/JoinTest.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.streams.client;
+
+import com.alibaba.fastjson.JSONObject;
+import java.io.Serializable;
+import org.apache.rocketmq.streams.client.transform.DataStream;
+import org.apache.rocketmq.streams.client.transform.JoinStream.JoinType;
+import org.apache.rocketmq.streams.common.functions.FilterFunction;
+import org.junit.Test;
+
+public class JoinTest implements Serializable {
+
+ @Test
+ public void testJoin() {
+ DataStream leftStream = (StreamBuilder.dataStream("namespace", "name")
+ .fromFile("/Users/yuanxiaodong/chris/sls_1000.txt")
+ .filter(new FilterFunction<JSONObject>() {
+
+ @Override
+ public boolean filter(JSONObject value) throws Exception {
+ if (value.getString("ProjectName") == null || value.getString("LogStore") == null) {
+ return true;
+ }
+ return false;
+ }
+ }));
+
+ DataStream rightStream = (StreamBuilder.dataStream("namespace", "name2")
+ .fromFile("/Users/yuanxiaodong/chris/sls_1000.txt")
+ .filter(new FilterFunction<JSONObject>() {
+
+ @Override
+ public boolean filter(JSONObject value) throws Exception {
+ if (value.getString("ProjectName") == null || value.getString("LogStore") == null) {
+ return true;
+ }
+ return false;
+ }
+ }));
+
+ leftStream.join(rightStream).setJoinType(JoinType.INNER_JOIN)
+ .setCondition("(ProjectName,==,ProjectName)&(LogStore,==,LogStore)")
+ .toDataSteam()
+ .toPrint()
+ .start();
+
+ }
+
+ @Test
+ public void testDim() {
+ DataStreamAction stream = (StreamBuilder.dataStream("namespace", "name")
+ .fromFile("/Users/yuanxiaodong/chris/sls_1000.txt")
+ .filter(new FilterFunction<JSONObject>() {
+
+ @Override
+ public boolean filter(JSONObject value) throws Exception {
+ if (value.getString("ProjectName") == null || value.getString("LogStore") == null) {
+ return true;
+ }
+ return false;
+ }
+ }))
+ .join("dburl", "dbUserName", "dbPassowrd", "tableNameOrSQL", 5)
+ .setCondition("(name,==,name)")
+ .toDataSteam()
+ .selectFields("name", "age", "address")
+ .toPrint();
+ stream.start();
+ ;
+ ;
+
+ }
+}
diff --git a/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/LeaseTest.java b/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/LeaseTest.java
new file mode 100644
index 0000000..1d3ec8a
--- /dev/null
+++ b/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/LeaseTest.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.streams.client;
+
+import java.util.Date;
+import org.apache.rocketmq.streams.common.component.ComponentCreator;
+import org.apache.rocketmq.streams.common.configure.ConfigureFileKey;
+import org.apache.rocketmq.streams.db.driver.DriverBuilder;
+import org.apache.rocketmq.streams.db.driver.JDBCDriver;
+import org.apache.rocketmq.streams.lease.LeaseComponent;
+import org.apache.rocketmq.streams.lease.model.LeaseInfo;
+import org.apache.rocketmq.streams.lease.service.ILeaseGetCallback;
+import org.junit.Test;
+
+import static org.junit.Assert.assertTrue;
+
+public class LeaseTest {
+
+ private String URL="";
+ protected String USER_NAME="";
+ protected String PASSWORD="";
+
+ public LeaseTest(){
+
+ //正式使用时,在配置文件配置
+ ComponentCreator.getProperties().put(ConfigureFileKey.CONNECT_TYPE,"DB");
+ ComponentCreator.getProperties().put(ConfigureFileKey.JDBC_URL,URL);//数据库连接url
+ ComponentCreator.getProperties().put(ConfigureFileKey.JDBC_USERNAME,USER_NAME);//用户名
+ ComponentCreator.getProperties().put(ConfigureFileKey.JDBC_PASSWORD,PASSWORD);//password
+
+ /**
+ * 创建lease info表
+ */
+ JDBCDriver driver= DriverBuilder.createDriver();
+ driver.execute(LeaseInfo.createTableSQL());
+ }
+
+ @Test
+ public void testLease() throws InterruptedException {
+ String leaseName="lease.test";
+ int leaseTime=5;
+ LeaseComponent.getInstance().getService().startLeaseTask(leaseName, leaseTime, new ILeaseGetCallback() {
+ @Override
+ public void callback(Date nextLeaseDate) {
+ System.out.println("I get lease");
+ }
+ });
+ assertTrue(LeaseComponent.getInstance().getService().hasLease(leaseName));
+ Thread.sleep(5000);
+ assertTrue(LeaseComponent.getInstance().getService().hasLease(leaseName));//会一直续约
+ Thread.sleep(5000);
+ assertTrue(LeaseComponent.getInstance().getService().hasLease(leaseName));//会一直续约
+ }
+
+ @Test
+ public void testLock() throws InterruptedException {
+ String name="dipper";
+ String lockName="lease.test";
+ int leaseTime=5;
+ boolean success=LeaseComponent.getInstance().getService().lock(name,lockName,leaseTime);//锁定5秒钟
+ assertTrue(success);//获取锁
+ Thread.sleep(6000);
+ assertTrue(!LeaseComponent.getInstance().getService().hasHoldLock(name,lockName));//超期释放
+ }
+
+ /**
+ * holdlock是一直持有锁,和租约的区别是,当释放锁后,无其他实例抢占
+ *
+ * @throws InterruptedException
+ */
+ @Test
+ public void testHoldLock() throws InterruptedException {
+ String name="dipper";
+ String lockName="lease.test";
+ int leaseTime=6;
+ boolean success=LeaseComponent.getInstance().getService().holdLock(name,lockName,leaseTime);//锁定5秒钟
+ assertTrue(success);//获取锁
+ Thread.sleep(8000);
+ assertTrue(LeaseComponent.getInstance().getService().hasHoldLock(name,lockName));//会自动续约,不会释放,可以手动释放
+ LeaseComponent.getInstance().getService().unlock(name,lockName);
+ assertTrue(!LeaseComponent.getInstance().getService().hasHoldLock(name,lockName));
+ }
+}
diff --git a/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/ORMUtilTest.java b/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/ORMUtilTest.java
new file mode 100644
index 0000000..a8ba9ab
--- /dev/null
+++ b/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/ORMUtilTest.java
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.streams.client;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.rocketmq.streams.common.component.ComponentCreator;
+import org.apache.rocketmq.streams.common.configure.ConfigureFileKey;
+import org.apache.rocketmq.streams.common.configurable.BasedConfigurable;
+import org.apache.rocketmq.streams.common.configurable.annotation.ENVDependence;
+import org.apache.rocketmq.streams.db.driver.orm.ORMUtil;
+import org.junit.Test;
+
+public class ORMUtilTest {
+ private String URL="";
+ protected String USER_NAME="";
+ protected String PASSWORD="";
+
+ public ORMUtilTest(){
+ //正式使用时,在配置文件配置
+ ComponentCreator.getProperties().put(ConfigureFileKey.JDBC_URL,URL);//数据库连接url
+ ComponentCreator.getProperties().put(ConfigureFileKey.JDBC_USERNAME,USER_NAME);//用户名
+ ComponentCreator.getProperties().put(ConfigureFileKey.JDBC_PASSWORD,PASSWORD);//password
+ }
+ @Test
+ public void testInsert(){
+ String namespace="org.apache.configuable.test";
+ List<Person> personList=new ArrayList<>();
+ for(int i=0;i<10;i++){
+ personList.add(createPerson(namespace,"chris"+i));
+ }
+ /**
+ * 不带数据库连接信息(url,userName,Password),使用ConfiguableComponet的连接信息
+ */
+ ORMUtil.batchIgnoreInto(personList);//批量插入,如果有唯一键冲突,替换
+ ORMUtil.batchIgnoreInto(personList);//批量插入,如果有唯一键冲突,忽略
+ ORMUtil.batchInsertInto(personList);////批量插入,如果有唯一键冲突,跑错
+ }
+
+ @Test
+ public void testQueryList(){
+ Map<String,Integer> paras=new HashMap<>();
+ paras.put("age",18);
+ List<Person> personList=ORMUtil.queryForList("select * from person where age >${age} limit 100",paras,Person.class);
+ }
+
+ @Test
+ public void testQueryOneRow(){
+ Person personPara=new Person();
+ personPara.setAge(18);
+ personPara.setName("chris1");
+ Person person=ORMUtil.queryForObject("select * from person where age =${age} and name='${name}' ",personPara,Person.class,URL,USER_NAME,PASSWORD);
+ }
+
+ /**
+ * 创建configuable对象
+ * @param namespace
+ * @return
+ */
+ protected Person createPerson(String namespace,String name){
+ Person person=new Person();
+ person.setName(name);
+ person.setAge(18);
+ person.setNameSpace(namespace);
+ person.setConfigureName("peronName");
+ person.setType("person");
+ return person;
+ }
+}
+
+
+class Person extends BasedConfigurable {
+ @ENVDependence
+ private String name;
+ private int age;
+ private Boolean isMale;
+ private List<String> addresses;
+ private Map<String, Integer> childName2Age;
+
+ public static Person createPerson(String namespace) {
+ Person person = new Person();
+ person.setNameSpace(namespace);
+ person.setType("person");
+ person.setConfigureName("Chris");
+ person.setName("Chris");
+ List<String> addresses = new ArrayList<>();
+ addresses.add("huilongguan");
+ addresses.add("shangdi");
+ person.setAddresses(addresses);
+ Map<String, Integer> childName2Age = new HashMap<>();
+ childName2Age.put("yuanyahan", 8);
+ childName2Age.put("yuanruxi", 4);
+ person.setChildName2Age(childName2Age);
+ person.setMale(true);
+ person.setAge(18);
+ return person;
+ }
+
+ @Override
+ public String toString() {
+ return "org.apache.rocketmq.streams.Person{" + "name='" + name + '\'' + ", age=" + age + ", isMale=" + isMale + ", addresses=" + addresses
+ + ", childName2Age=" + childName2Age + '}';
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+
+ public Boolean getMale() {
+ return isMale;
+ }
+
+ public void setMale(Boolean male) {
+ isMale = male;
+ }
+
+ public List<String> getAddresses() {
+ return addresses;
+ }
+
+ public void setAddresses(List<String> addresses) {
+ this.addresses = addresses;
+ }
+
+ public Map<String, Integer> getChildName2Age() {
+ return childName2Age;
+ }
+
+ public void setChildName2Age(Map<String, Integer> childName2Age) {
+ this.childName2Age = childName2Age;
+ }
+
+ @Override
+ public Object clone() {
+ Person person = null;
+ try {
+ person = (Person)super.clone();
+ } catch (CloneNotSupportedException e) {
+ System.out.println("clone error " + e);
+ }
+ return person;
+ }
+}
diff --git a/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/SplitTest.java b/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/SplitTest.java
new file mode 100644
index 0000000..56ab5ae
--- /dev/null
+++ b/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/SplitTest.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.streams.client;
+
+import com.alibaba.fastjson.JSONObject;
+import java.io.Serializable;
+import org.apache.rocketmq.streams.client.transform.DataStream;
+import org.apache.rocketmq.streams.client.transform.SplitStream;
+import org.apache.rocketmq.streams.common.functions.FilterFunction;
+import org.apache.rocketmq.streams.common.functions.SplitFunction;
+import org.junit.Test;
+
+public class SplitTest implements Serializable {
+
+ @Test
+ public void testUnion() {
+ DataStream stream = (StreamBuilder.dataStream("namespace", "name")
+ .fromFile("/Users/yuanxiaodong/chris/sls_1000.txt")
+ .filter(new FilterFunction<JSONObject>() {
+
+ @Override
+ public boolean filter(JSONObject value) throws Exception {
+ if (value.getString("ProjectName") == null || value.getString("LogStore") == null) {
+ return true;
+ }
+ return false;
+ }
+ }));
+
+ SplitStream splitStream = stream.split(new SplitFunction<JSONObject>() {
+ @Override
+ public String split(JSONObject o) {
+ if (o.getInteger("age") < 18) {
+ return "children";
+ } else if (o.getInteger("age") >= 18) {
+ return "adult";
+ }
+ return null;
+ }
+ });
+
+ DataStream children = splitStream.select("children");
+ DataStream adult = splitStream.select("adult");
+ children.union(adult)
+ .toPrint()
+ .start();
+
+ }
+
+ @Test
+ public void testDim() {
+ DataStreamAction stream = (StreamBuilder.dataStream("namespace", "name")
+ .fromFile("/Users/yuanxiaodong/chris/sls_1000.txt")
+ .filter(new FilterFunction<JSONObject>() {
+
+ @Override
+ public boolean filter(JSONObject value) throws Exception {
+ if (value.getString("ProjectName") == null || value.getString("LogStore") == null) {
+ return true;
+ }
+ return false;
+ }
+ }))
+ .join("dburl", "dbUserName", "dbPassowrd", "tableNameOrSQL", 5)
+ .setCondition("(name,==,name)")
+ .toDataSteam()
+ .selectFields("name", "age", "address")
+ .toPrint();
+
+ }
+}
diff --git a/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/UnionTest.java b/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/UnionTest.java
new file mode 100644
index 0000000..1b49185
--- /dev/null
+++ b/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/UnionTest.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.streams.client;
+
+import com.alibaba.fastjson.JSONObject;
+import java.io.Serializable;
+import org.apache.rocketmq.streams.client.transform.DataStream;
+import org.apache.rocketmq.streams.common.functions.FilterFunction;
+import org.junit.Test;
+
+public class UnionTest implements Serializable {
+
+ @Test
+ public void testUnion() {
+ DataStream leftStream = (StreamBuilder.dataStream("namespace", "name")
+ .fromFile("/Users/yuanxiaodong/chris/sls_1000.txt")
+ .filter(new FilterFunction<JSONObject>() {
+
+ @Override
+ public boolean filter(JSONObject value) throws Exception {
+ if (value.getString("ProjectName") == null || value.getString("LogStore") == null) {
+ return true;
+ }
+ return false;
+ }
+ }));
+
+ DataStream rightStream = (StreamBuilder.dataStream("namespace", "name2")
+ .fromFile("/Users/yuanxiaodong/chris/sls_1000.txt")
+ .filter(new FilterFunction<JSONObject>() {
+
+ @Override
+ public boolean filter(JSONObject value) throws Exception {
+ if (value.getString("ProjectName") == null || value.getString("LogStore") == null) {
+ return true;
+ }
+ return false;
+ }
+ }));
+
+ leftStream.union(rightStream).toPrint().start();
+ }
+
+ @Test
+ public void testDim() {
+ DataStreamAction stream = (StreamBuilder.dataStream("namespace", "name")
+ .fromFile("/Users/yuanxiaodong/chris/sls_1000.txt")
+ .filter(new FilterFunction<JSONObject>() {
+
+ @Override
+ public boolean filter(JSONObject value) throws Exception {
+ if (value.getString("ProjectName") == null || value.getString("LogStore") == null) {
+ return true;
+ }
+ return false;
+ }
+ }))
+ .join("dburl", "dbUserName", "dbPassowrd", "tableNameOrSQL", 5)
+ .setCondition("(name,==,name)")
+ .toDataSteam()
+ .selectFields("name", "age", "address")
+ .toPrint();
+
+ stream.start();
+
+ }
+}
diff --git a/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/WindowTest.java b/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/WindowTest.java
new file mode 100644
index 0000000..69d98b2
--- /dev/null
+++ b/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/WindowTest.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.streams.client;
+
+import com.alibaba.fastjson.JSONObject;
+
+import java.io.Serializable;
+
+import org.apache.rocketmq.streams.client.transform.window.Time;
+import org.apache.rocketmq.streams.client.transform.window.TumblingWindow;
+import org.apache.rocketmq.streams.common.functions.ForEachFunction;
+import org.apache.rocketmq.streams.common.functions.MapFunction;
+import org.junit.Test;
+
+public class WindowTest implements Serializable {
+
+ @Test
+ public void testWindow() {
+ StreamBuilder.dataStream("namespace", "name")
+ .fromFile("/Users/duheng/project/opensource/sls_100.txt", false)
+ .map((MapFunction<JSONObject, String>)message -> JSONObject.parseObject(message))
+ .window(TumblingWindow.of(Time.seconds(5)))
+ .groupBy("ProjectName", "LogStore")
+ .setLocalStorageOnly(true)
+ .count("total")
+ .sum("OutFlow", "OutFlow")
+ .sum("InFlow", "InFlow")
+ .toDataSteam()
+ .forEach(new ForEachFunction<JSONObject>() {
+ protected int sum = 0;
+
+ @Override
+ public void foreach(JSONObject o) {
+ int total = o.getInteger("total");
+ sum = sum + total;
+ o.put("sum(total)", sum);
+ }
+ }).toPrint().start();
+
+ }
+
+ // @Test
+ // public void testWindowFromMetaq() throws InterruptedException {
+ // String topic = "TOPIC_DIPPER_SYSTEM_MSG_4";
+ // StreamBuilder.dataStream("namespace", "name")
+ // .fromFile("/Users/yuanxiaodong/chris/sls_100.txt", true)
+ // .toRocketmq(topic)
+ // .asyncStart();
+ //
+ // StreamBuilder.dataStream("namespace", "name1")
+ // .fromRocketmq(topic, "chris", true)
+ // .window(TumblingWindow.of(Time.seconds(5)))
+ // .groupby("ProjectName", "LogStore")
+ // .setLocalStorageOnly(true)
+ // .count("total")
+ // .sum("OutFlow", "OutFlow")
+ // .sum("InFlow", "inflow")
+ // .toDataSteam()
+ // .forEach(new ForEachFunction<JSONObject>() {
+ // protected int sum = 0;
+ //
+ // @Override
+ // public void foreach(JSONObject o) {
+ // int total = o.getInteger("total");
+ // sum = sum + total;
+ // o.put("sum(total)", sum);
+ // }
+ // }).toPrint().start();
+ // }
+
+}
diff --git a/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/windows/AbstractWindowFireModeTest.java b/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/windows/AbstractWindowFireModeTest.java
new file mode 100644
index 0000000..c102f1f
--- /dev/null
+++ b/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/windows/AbstractWindowFireModeTest.java
@@ -0,0 +1,189 @@
+package org.apache.rocketmq.streams.client.windows;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.alibaba.fastjson.JSONObject;
+
+import org.apache.rocketmq.streams.client.transform.DataStream;
+import org.apache.rocketmq.streams.client.transform.window.Time;
+import org.apache.rocketmq.streams.client.transform.window.TumblingWindow;
+import org.apache.rocketmq.streams.common.functions.ForEachFunction;
+import org.apache.rocketmq.streams.common.functions.MapFunction;
+import org.apache.rocketmq.streams.common.utils.DateUtil;
+
+
+public abstract class AbstractWindowFireModeTest implements Serializable {
+ protected Date date=new Date();
+ public AbstractWindowFireModeTest(){
+
+ date.setYear(2021-1900);
+ date.setMonth(6);
+ date.setDate(14);
+ date.setHours(12);
+ date.setMinutes(1);
+ date.setSeconds(0);
+
+ }
+
+ public void testWindowFireMode0(boolean isLocalOnly) throws InterruptedException {
+ testWindowFireMode1(isLocalOnly,5);
+ }
+
+ public void testWindowFireMode0(boolean isLocalOnly,int windowSize) throws InterruptedException {
+
+ createSourceDataStream().map(new MapFunction<JSONObject, String>() {
+ int count=0;
+ Long time=null;
+ @Override
+ public JSONObject map(String message) throws Exception {
+
+ if(time==null){
+ time=date.getTime();
+ }else {
+ time+=count;
+ }
+ count++;
+ JSONObject msg=JSONObject.parseObject(message);
+
+ msg.put("logTime",time);
+
+ return msg;
+ }
+ })
+ .window(TumblingWindow.of(Time.seconds(windowSize)))
+ .groupBy("ProjectName", "LogStore")
+ .setLocalStorageOnly(isLocalOnly)
+ .setMaxMsgGap(isLocalOnly?10L:20L)
+ .setTimeField("logTime")
+ .count("total")
+ .sum("OutFlow", "OutFlow")
+ .sum("InFlow", "inflow")
+ .toDataSteam()
+ .forEach(new ForEachFunction<JSONObject>() {
+ AtomicInteger sum = new AtomicInteger(0) ;
+ @Override
+ public synchronized void foreach(JSONObject o) {
+ int total = o.getInteger("total");
+ o.put("sum(total)", sum.addAndGet(total));
+ }
+ }).toPrint().start();
+ }
+ public void testWindowFireMode1(boolean isLocalOnly) throws InterruptedException {
+ testWindowFireMode1(isLocalOnly,5);
+ }
+ public void testWindowFireMode1(boolean isLocalOnly,int windowSize) throws InterruptedException {
+ AtomicInteger sum = new AtomicInteger(0) ;
+ createSourceDataStream()
+ //.map(new MapFunction<JSONObject, String>() {
+ // AtomicInteger COUNT=new AtomicInteger(0);
+ // Long time;
+ // @Override
+ // public JSONObject map(String message) throws Exception {
+ //
+ // if(time==null){
+ // time=date.getTime();
+ // }else {
+ // int count=COUNT.incrementAndGet();
+ // time+=count;
+ // }
+ // JSONObject msg=JSONObject.parseObject(message);
+ //
+ // msg.put("logTime",time);
+ // return msg;
+ // }
+ //})
+ .window(TumblingWindow.of(Time.seconds(windowSize)))
+ .setTimeField("logTime")
+ .fireMode(1)
+ .setMaxMsgGap(isLocalOnly?20L:20L)
+ .waterMark(100000000)
+ .groupBy("ProjectName", "LogStore")
+ .setLocalStorageOnly(isLocalOnly)
+ .count("total")
+ .sum("OutFlow", "OutFlow")
+ .sum("InFlow", "InFlow")
+ .toDataSteam()
+ .forEach(new ForEachFunction<JSONObject>() {
+
+
+ @Override
+ public synchronized void foreach(JSONObject o) {
+ int total = o.getInteger("total");
+ o.put("sum(total)", sum.addAndGet(total));
+ }
+ }).toPrint().start();
+ }
+
+ public void testWindowFireMode2(boolean isLocalOnly){
+ long time=new Date().getTime();
+ System.out.println(DateUtil.getCurrentTimeString());
+ createSourceDataStream()
+ .map(new MapFunction<JSONObject, String>() {
+ int count=0;
+ @Override
+ public JSONObject map(String message) throws Exception {
+
+ JSONObject msg=JSONObject.parseObject(message);
+ long time= msg.getLong("logTime");
+ Date date=new Date(time);
+ date.setYear(2021-1900);
+ date.setMonth(6);
+ date.setDate(14);
+ msg.put("logTime",date.getTime()+count++);
+ return msg;
+ }
+ })
+ .window(TumblingWindow.of(Time.seconds(5)))
+ .setTimeField("logTime")
+ .setMaxMsgGap(isLocalOnly?5L:20L)
+ .fireMode(1)
+ .waterMark(100000000)
+ .groupBy("ProjectName", "LogStore")
+ .setLocalStorageOnly(isLocalOnly)
+ .count("total")
+ .sum("OutFlow", "OutFlow")
+ .sum("InFlow", "InFlow")
+ .toDataSteam()
+ .map(new MapFunction<JSONObject, JSONObject>() {
+ long time=new Date().getTime();
+ @Override
+ public JSONObject map(JSONObject message) throws Exception {
+ message.put("name","chris");
+ message.put("time",time++);
+ return message;
+ }
+ })
+ .window(TumblingWindow.of(Time.seconds(5)))
+ .fireMode(2).waterMark(100000000)
+ .setMaxMsgGap(80L)
+ .groupBy("name")
+ .setTimeField("time")
+ .sum("total","sum_total")
+ .setLocalStorageOnly(true)
+ .toDataSteam()
+ .forEach(new ForEachFunction<JSONObject>() {
+ AtomicInteger sum = new AtomicInteger(0) ;
+ Map<String,Integer> map=new HashMap<>();
+ @Override
+ public synchronized void foreach(JSONObject o) {
+ String windowInstanceId=o.getString("windowInstanceId");
+ Integer oldValue=map.get(windowInstanceId);
+ int total = o.getInteger("sum_total");
+ if(oldValue!=null){
+ total=total-oldValue;
+ }
+ int nowValue=sum.addAndGet(total);
+ map.put(windowInstanceId,total);
+ o.put("sum(total)", nowValue);
+ }
+ }).toPrint().start();
+
+ }
+
+
+ protected abstract DataStream createSourceDataStream();
+}
diff --git a/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/windows/WindowFromFileTest.java b/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/windows/WindowFromFileTest.java
new file mode 100644
index 0000000..71a2593
--- /dev/null
+++ b/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/windows/WindowFromFileTest.java
@@ -0,0 +1,158 @@
+package org.apache.rocketmq.streams.client.windows;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.alibaba.fastjson.JSONObject;
+
+import org.apache.rocketmq.streams.client.StreamBuilder;
+import org.apache.rocketmq.streams.client.transform.DataStream;
+import org.apache.rocketmq.streams.common.utils.FileUtil;
+import org.apache.rocketmq.streams.common.utils.MapKeyUtil;
+import org.apache.rocketmq.streams.common.utils.PrintUtil;
+import org.apache.rocketmq.streams.common.utils.StringUtil;
+import org.apache.rocketmq.streams.db.driver.batchloader.IRowOperator;
+import org.apache.rocketmq.streams.db.driver.orm.ORMUtil;
+import org.apache.rocketmq.streams.window.model.WindowInstance;
+import org.apache.rocketmq.streams.window.operator.AbstractWindow;
+import org.apache.rocketmq.streams.window.operator.impl.WindowOperator;
+import org.apache.rocketmq.streams.window.state.impl.WindowValue;
+import org.apache.rocketmq.streams.window.storage.WindowStorage;
+import org.junit.Test;
+
+public class WindowFromFileTest extends AbstractWindowFireModeTest {
+ protected DataStream createSourceDataStream(){
+ return StreamBuilder.dataStream("namespace", "name1")
+ .fromFile("/Users/yuanxiaodong/chris/sls_100000.txt",false);
+ }
+
+ /**
+ *
+ * @throws InterruptedException
+ */
+ @Test
+ public void testWindowFireMode0AndNoTimeField() throws InterruptedException {
+ super.testWindowFireMode0(false);
+ }
+
+
+
+ @Test
+ public void testWindowFireMode1() throws InterruptedException {
+ super.testWindowFireMode1(false);
+ }
+
+
+
+ @Test
+ public void testWindowFireMode2() {
+ super.testWindowFireMode2(false);
+ }
+
+
+
+ @Test
+ public void testSLS10(){
+ Map<String,Integer> map=create();
+
+ System.out.println(map.size());
+ PrintUtil.print(map);
+ }
+
+
+ @Test
+ public void testLoadData() throws InterruptedException {
+ Map<String,Integer> map=create();
+ WindowOperator windowOperator=new WindowOperator();
+ windowOperator.setNameSpace("namespace");
+ windowOperator.setConfigureName("name1");
+ WindowInstance windowInstance=windowOperator.createWindowInstance("2021-07-14 02:00:00","2021-07-14 03:00:00","2021-07-14 03:00:00","1");
+ WindowStorage storage= new WindowStorage();
+ storage.loadSplitData2Local("1", windowInstance.createWindowInstanceId(), WindowValue.class, new IRowOperator() {
+ @Override
+ public void doProcess(Map<String, Object> row) {
+ WindowValue windowValue = ORMUtil.convert(row, WindowValue.class);
+ String groupBy=windowValue.getGroupBy();
+ if(!map.containsKey(groupBy)){
+
+ System.out.println(groupBy+" ======");
+ }else {
+ Integer count=(Integer)windowValue.getComputedColumnResultByKey("total");
+ int mapCount=map.get(groupBy);
+ if(count!=mapCount){
+ System.out.println(groupBy+" ======");
+ }
+ }
+ }
+ });
+
+
+ Thread.sleep(10000000l);
+ }
+
+
+ protected Map<String,Integer> create(){
+ List<String> lines= FileUtil.loadFileLine("/Users/yuanxiaodong/chris/sls_10000.txt");
+ Map<String,Integer> map=new HashMap<>();
+ for(String line:lines){
+ JSONObject msg=JSONObject.parseObject(line);
+ String projectName=msg.getString("ProjectName");
+ String logstore=msg.getString("LogStore");
+
+ String key= MapKeyUtil.createKey(projectName,logstore);
+
+ if (StringUtil.isEmpty(key)) {
+ key="<null>";
+ }
+ Integer count=map.get(key);
+ if(count==null){
+ map.put(key,1);
+ }else {
+ count++;
+ map.put(key,count);
+ }
+ }
+ return map;
+ }
+
+
+ @Test
+ public void testFileResult(){
+ createFile(3);
+ }
+
+
+ protected void createFile(int count){
+
+ date.setYear(2021-1900);
+ date.setMonth(6);
+ date.setDate(27);
+ date.setHours(12);
+ date.setMinutes(1);
+ date.setSeconds(0);
+ Long time=null;
+ for(int i=0;i<count;i++){
+ List<String> lines= FileUtil.loadFileLine("/Users/yuanxiaodong/chris/sls_100000.txt");
+ List<String> msgs=new ArrayList<>();
+ for(String line:lines){
+ JSONObject msg=JSONObject.parseObject(line);
+ if(time==null){
+ time=date.getTime();
+ }else {
+ time=time+1;
+ }
+ msg.put("logTime",time);
+ AbstractWindow window=new WindowOperator();
+ window.setSizeInterval(5);
+ window.setTimeUnitAdjust(1);
+ window.setTimeFieldName("logTime");
+ //WindowInstance windowInstance= window.queryOrCreateWindowInstance(new Message(msg),"1").get(0);
+ msgs.add(msg.toJSONString());
+ }
+ FileUtil.write("/Users/yuanxiaodong/chris/sls_100000_time_"+i+".txt",msgs);
+ }
+ }
+
+}
diff --git a/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/windows/WindowFromMetaq.java b/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/windows/WindowFromMetaq.java
new file mode 100644
index 0000000..235ce3d
--- /dev/null
+++ b/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/windows/WindowFromMetaq.java
@@ -0,0 +1,47 @@
+package org.apache.rocketmq.streams.client.windows;
+
+import org.apache.rocketmq.streams.client.StreamBuilder;
+import org.apache.rocketmq.streams.client.transform.DataStream;
+import org.junit.Test;
+
+public class WindowFromMetaq extends AbstractWindowFireModeTest {
+
+ String topic = "TOPIC_DIPPER_SYSTEM_MSG_5";
+ @Test
+ public void testWindowFireMode0() throws InterruptedException {
+ super.testWindowFireMode0(true);
+ }
+
+
+
+ @Test
+ public void testWindowFireMode1() throws InterruptedException {
+ super.testWindowFireMode1(true);
+ }
+
+
+
+ @Test
+ public void testWindowFireMode2() {
+ super.testWindowFireMode2(true);
+ }
+
+
+ @Test
+ public void testWindowToMetaq() throws InterruptedException {
+
+ long start=System.currentTimeMillis();
+ StreamBuilder.dataStream("namespace", "name")
+ .fromFile("/Users/yuanxiaodong/chris/sls_10.txt", true)
+ .toMetaq(topic)
+ .start();
+ }
+
+
+ protected DataStream createSourceDataStream(){
+ return StreamBuilder.dataStream("namespace", "name1")
+ .fromRocketmq(topic,"chris1",true);
+ }
+
+
+}
diff --git a/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/windows/WindowHighAvailabilityTest.java b/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/windows/WindowHighAvailabilityTest.java
new file mode 100644
index 0000000..cbca7bb
--- /dev/null
+++ b/rocketmq-streams-clients/src/test/java/org/apache/rocketmq/streams/client/windows/WindowHighAvailabilityTest.java
@@ -0,0 +1,131 @@
+package org.apache.rocketmq.streams.client.windows;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.alibaba.fastjson.JSONObject;
+
+import org.apache.rocketmq.streams.client.StreamBuilder;
+import org.apache.rocketmq.streams.client.transform.DataStream;
+import org.apache.rocketmq.streams.common.utils.FileUtil;
+import org.apache.rocketmq.streams.common.utils.MapKeyUtil;
+import org.apache.rocketmq.streams.common.utils.StringUtil;
+import org.apache.rocketmq.streams.db.driver.batchloader.BatchRowLoader;
+import org.apache.rocketmq.streams.db.driver.batchloader.IRowOperator;
+import org.apache.rocketmq.streams.db.driver.orm.ORMUtil;
+import org.apache.rocketmq.streams.window.model.WindowInstance;
+import org.apache.rocketmq.streams.window.operator.impl.WindowOperator;
+import org.apache.rocketmq.streams.window.state.impl.WindowValue;
+import org.apache.rocketmq.streams.window.storage.WindowStorage;
+import org.junit.Test;
+
+public class WindowHighAvailabilityTest extends AbstractWindowFireModeTest {
+ protected DataStream createSourceDataStream(){
+ return StreamBuilder.dataStream("namespace", "name1")
+ .fromFile("/Users/yuanxiaodong/chris/sls_100000.txt",false);
+ }
+
+ /**
+ *
+ * @throws InterruptedException
+ */
+ @Test
+ public void testWindowFireMode0AndNoTimeField() throws InterruptedException {
+ super.testWindowFireMode0(false,60*60);
+ }
+
+
+
+ @Test
+ public void testWindowFireMode1() throws InterruptedException {
+ super.testWindowFireMode1(false,60*5);
+ }
+
+
+
+ @Test
+ public void testWindowFireMode2() {
+ super.testWindowFireMode2(false);
+ }
+
+ @Test
+ public void testLoadData() throws InterruptedException {
+ Map<String,Integer> map=create();
+ WindowOperator windowOperator=new WindowOperator();
+ windowOperator.setNameSpace("namespace");
+ windowOperator.setConfigureName("name1_window_10001");
+ WindowInstance windowInstance=windowOperator.createWindowInstance("2021-07-14 02:00:00","2021-07-14 03:00:00","2021-07-14 03:00:00","1");
+
+ WindowStorage storage= new WindowStorage();
+ System.out.println("1;namespace;name1_window_10001;name1_window_10001;2021-07-14 02:00:00;2021-07-14 03:00:00");
+ System.out.println(windowInstance.createWindowInstanceId());
+ System.out.println(StringUtil.createMD5Str(windowInstance.createWindowInstanceId()));
+ Map<String, WindowValue> windowValueMap=new HashMap<>();
+ AtomicInteger sum=new AtomicInteger(0);
+ BatchRowLoader batchRowLoader = new BatchRowLoader("partition_num",
+ "select * from " + ORMUtil.getTableName(WindowValue.class) + " where window_instance_partition_id ='"
+ + StringUtil.createMD5Str(windowInstance.createWindowInstanceId() )+ "'", new IRowOperator(){
+
+ @Override
+ public void doProcess(Map<String, Object> row) {
+ WindowValue windowValue = ORMUtil.convert(row, WindowValue.class);
+
+ String groupBy=windowValue.getGroupBy();
+ windowValueMap.put(groupBy,windowValue);
+ if(!map.containsKey(groupBy)){
+
+ System.out.println(groupBy+" ======");
+ }else {
+ Integer count=(Integer)windowValue.getComputedColumnResultByKey("total");
+ int mapCount=map.get(groupBy);
+ if(count!=mapCount){
+ System.out.println(groupBy+" ======");
+ }
+ //System.out.println(groupBy+" "+count);
+ sum.addAndGet(count);
+
+ }
+ }
+ });
+ batchRowLoader.startLoadData();
+
+ System.out.println("sum "+sum.get());
+ for(String key:map.keySet()){
+ if(windowValueMap.containsKey(key)==false){
+ System.out.println(key+" "+map.get(key));
+ }
+ }
+
+ Thread.sleep(10000000l);
+ }
+
+
+ protected Map<String,Integer> create(){
+ List<String> lines= FileUtil.loadFileLine("/Users/yuanxiaodong/chris/sls_10000.txt");
+ Map<String,Integer> map=new HashMap<>();
+ for(String line:lines){
+ JSONObject msg=JSONObject.parseObject(line);
+ String projectName=msg.getString("ProjectName");
+ String logstore=msg.getString("LogStore");
+
+ String key= MapKeyUtil.createKey(projectName,logstore);
+
+ if (StringUtil.isEmpty(key)) {
+ key="<null>";
+ }
+ Integer count=map.get(key);
+ if(count==null){
+ map.put(key,1);
+ }else {
+ count++;
+ map.put(key,count);
+ }
+ }
+ return map;
+ }
+
+
+
+}
diff --git a/rocketmq-streams-clients/src/test/resources/log4j.xml b/rocketmq-streams-clients/src/test/resources/log4j.xml
new file mode 100755
index 0000000..a1afd37
--- /dev/null
+++ b/rocketmq-streams-clients/src/test/resources/log4j.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<!DOCTYPE log4j:configuration SYSTEM "http://toolkit.alibaba-inc.com/dtd/log4j/log4j.dtd">
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+
+ <appender name="Console" class="org.apache.log4j.ConsoleAppender">
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%d{ISO8601} %l [%t] %-5p - %m%n%n"/>
+ </layout>
+ <filter class="org.apache.log4j.varia.LevelRangeFilter">
+ <param name="LevelMin" value="INFO"/>
+ <param name="LevelMax" value="ERROR"/>
+ </filter>
+ </appender>
+
+ <root>
+ <priority value="INFO"/>
+ <appender-ref ref="Console"/>
+ </root>
+
+</log4j:configuration>
\ No newline at end of file
diff --git a/rocketmq-streams-dim/pom.xml b/rocketmq-streams-dim/pom.xml
new file mode 100644
index 0000000..1175370
--- /dev/null
+++ b/rocketmq-streams-dim/pom.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<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>
+
+ <parent>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-streams</artifactId>
+ <version>2.0.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>rocketmq-streams-dim</artifactId>
+ <name>ROCKETMQ STREAMS :: dim</name>
+ <packaging>jar</packaging>
+ <properties>
+ <file_encoding>UTF-8</file_encoding>
+ <project.build.sourceEncoding>${file_encoding}</project.build.sourceEncoding>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-streams-filter</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-streams-channel-http</artifactId>
+ </dependency>
+ </dependencies>
+
+
+</project>
diff --git a/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/DimComponent.java b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/DimComponent.java
new file mode 100644
index 0000000..8602b5d
--- /dev/null
+++ b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/DimComponent.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.dim;
+
+import java.util.Properties;
+
+import org.apache.rocketmq.streams.common.component.AbstractComponent;
+import org.apache.rocketmq.streams.common.component.ComponentCreator;
+import org.apache.rocketmq.streams.dim.service.IDimService;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.rocketmq.streams.configurable.ConfigurableComponent;
+import org.apache.rocketmq.streams.dim.service.impl.DimServiceImpl;
+
+public class DimComponent extends AbstractComponent<IDimService> {
+
+ private static final Log LOG = LogFactory.getLog(DimComponent.class);
+
+ // private transient Map<String, DBDim> nameListMap = new HashMap<>();
+
+ protected transient ConfigurableComponent configurableComponent;
+ private transient IDimService dimService;
+
+ public static DimComponent getInstance(String namespace) {
+ return ComponentCreator.getComponent(namespace, ComponentCreator.class);
+ }
+
+ @Override
+ protected boolean startComponent(String namespace) {
+ configurableComponent = ComponentCreator.getComponent(namespace, ConfigurableComponent.class);
+ dimService = new DimServiceImpl(configurableComponent);
+ return true;
+ }
+
+ @Override
+ protected boolean initProperties(Properties properties) {
+ return true;
+ }
+
+ @Override
+ public boolean stop() {
+ return true;
+ }
+
+ @Override
+ public IDimService getService() {
+ return dimService;
+ }
+}
diff --git a/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/builder/DimBuilder.java b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/builder/DimBuilder.java
new file mode 100644
index 0000000..508bb19
--- /dev/null
+++ b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/builder/DimBuilder.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.dim.builder;
+
+import org.apache.rocketmq.streams.dim.model.DBDim;
+import org.apache.rocketmq.streams.common.utils.StringUtil;
+
+public class DimBuilder {
+
+ private String url;
+ private String password;
+ private String userName;
+ protected Long pollingTime = 60L; // 同步数据的事件间隔
+ private String jdbcDriver = "com.mysql.jdbc.Driver";
+
+ public DimBuilder(String url, String userName, String password) {
+ this.url = url;
+ this.password = password;
+ this.userName = userName;
+ }
+
+ public DBDim createDim(String namespace, String name, String sqlOrTableName) {
+ DBDim nameList = new DBDim();
+ nameList.setNameSpace(namespace);
+ if (StringUtil.isNotEmpty(name)) {
+ nameList.setConfigureName(name);
+ }
+ String sql = sqlOrTableName;
+ if (sqlOrTableName.split(" ").length == 1) {
+ sql = "select * from " + sqlOrTableName + " limit 500000";
+ }
+ nameList.setSql(sql);
+ nameList.setJdbcdriver(jdbcDriver);
+ nameList.setPollingTimeMintue(pollingTime);
+ nameList.setUrl(url);
+ nameList.setUserName(userName);
+ nameList.setPassword(password);
+ return nameList;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public void setUserName(String userName) {
+ this.userName = userName;
+ }
+
+ public Long getPollingTime() {
+ return pollingTime;
+ }
+
+ public void setPollingTime(Long pollingTime) {
+ this.pollingTime = pollingTime;
+ }
+
+ public String getJdbcDriver() {
+ return jdbcDriver;
+ }
+
+ public void setJdbcDriver(String jdbcDriver) {
+ this.jdbcDriver = jdbcDriver;
+ }
+}
diff --git a/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/function/expression/InExpressionResource.java b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/function/expression/InExpressionResource.java
new file mode 100644
index 0000000..c8a13a7
--- /dev/null
+++ b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/function/expression/InExpressionResource.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.dim.function.expression;
+
+import org.apache.rocketmq.streams.dim.model.DBDim;
+import org.apache.rocketmq.streams.filter.context.RuleContext;
+import org.apache.rocketmq.streams.filter.function.expression.AbstractExpressionFunction;
+import org.apache.rocketmq.streams.filter.operator.Rule;
+import org.apache.rocketmq.streams.filter.operator.expression.Expression;
+import org.apache.rocketmq.streams.script.annotation.Function;
+import org.apache.rocketmq.streams.script.annotation.FunctionMethod;
+import org.apache.rocketmq.streams.script.annotation.FunctionMethodAilas;
+
+import java.util.Map;
+
+@Function
+public class InExpressionResource extends AbstractExpressionFunction {
+
+ /**
+ * value格式 :resourceName.fieldName。如果只有单列,可以省略.fieldname
+ *
+ * @param expression
+ * @param context
+ * @param rule
+ * @return
+ */
+ @FunctionMethod(value = "in_expression_resouce", alias = "in_resouce")
+ @FunctionMethodAilas("in_expression_resouce(resourceName->(varName,functionName,value)&((varName,functionName,"
+ + "value)|(varName,functionName,value)))")
+ @Override
+ public Boolean doExpressionFunction(Expression expression, RuleContext context, Rule rule) {
+ return match(expression, context, rule, false);
+ }
+
+ protected Boolean match(Expression expression, RuleContext context, Rule rule, boolean supportRegex) {
+ Object value = expression.getValue();
+ if (value == null) {
+ return null;
+ }
+ String valueStr = String.valueOf(value);
+ String[] valueArray = valueStr.split("->");
+ String dataResourceNamespace = rule.getNameSpace();
+ String dataResourceName = null;
+ String expressionStr = null;
+ if (valueArray.length == 2) {
+ dataResourceName = valueArray[0];
+ expressionStr = valueArray[1];
+ }
+ if (valueArray.length > 2) {
+ dataResourceNamespace = valueArray[0];
+ dataResourceName = valueArray[1];
+ expressionStr = valueArray[2];
+ }
+
+ DBDim dataResource =
+ (DBDim)context.getConfigurableService().queryConfigurableByIdent(DBDim.TYPE, dataResourceName);
+ if (dataResource == null) {
+ return null;
+ }
+ Map<String, Object> row = dataResource.matchExpression(expressionStr, context.getMessage().getMessageBody());
+ if (row != null && row.size() > 0) {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/function/expression/NotInExpressionResource.java b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/function/expression/NotInExpressionResource.java
new file mode 100644
index 0000000..752276c
--- /dev/null
+++ b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/function/expression/NotInExpressionResource.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.dim.function.expression;
+
+import org.apache.rocketmq.streams.filter.context.RuleContext;
+import org.apache.rocketmq.streams.filter.operator.Rule;
+import org.apache.rocketmq.streams.filter.operator.expression.Expression;
+import org.apache.rocketmq.streams.script.annotation.Function;
+import org.apache.rocketmq.streams.script.annotation.FunctionMethod;
+import org.apache.rocketmq.streams.script.annotation.FunctionMethodAilas;
+
+@Function
+public class NotInExpressionResource extends InExpressionResource {
+
+ /**
+ * value格式 :resourceName.fieldName。如果只有单列,可以省略.fieldname
+ *
+ * @param expression
+ * @param context
+ * @param rule
+ * @return
+ */
+ @FunctionMethod("not_in_expression_resouce")
+ @FunctionMethodAilas("not_in_expression_resouce(resourceName->(varName,functionName,value)&((varName,"
+ + "functionName,value)|(varName,functionName,value)))")
+ @Override
+ public Boolean doExpressionFunction(Expression expression, RuleContext context, Rule rule) {
+ return !match(expression, context, rule, false);
+ }
+
+}
diff --git a/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/function/script/IntelligenceFunction.java b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/function/script/IntelligenceFunction.java
new file mode 100644
index 0000000..c2ffb88
--- /dev/null
+++ b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/function/script/IntelligenceFunction.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.dim.function.script;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.rocketmq.streams.common.component.ComponentCreator;
+import org.apache.rocketmq.streams.configurable.ConfigurableComponent;
+import org.apache.rocketmq.streams.dim.DimComponent;
+import org.apache.rocketmq.streams.dim.intelligence.AbstractIntelligenceCache;
+import org.apache.rocketmq.streams.script.context.FunctionContext;
+import org.apache.rocketmq.streams.script.annotation.Function;
+import org.apache.rocketmq.streams.script.annotation.FunctionMethod;
+import org.apache.rocketmq.streams.common.context.IMessage;
+import org.apache.rocketmq.streams.script.utils.FunctionUtils;
+import org.apache.rocketmq.streams.common.utils.StringUtil;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+@Function
+public class IntelligenceFunction {
+
+ private static final Log LOG = LogFactory.getLog(IntelligenceFunction.class);
+
+ private DimComponent nameListComponent;
+
+ @FunctionMethod(value = "intelligence", alias = "qingbao")
+ public void intelligence(IMessage message, FunctionContext context, String namespace, String nameListName, String intelligenceFieldName, String asName) {
+ intelligenceInner(message, context, namespace, nameListName, intelligenceFieldName, asName, true);
+ }
+
+ @FunctionMethod(value = "left_join_intelligence", alias = "left_join_qingbao")
+ public void intelligenceLeftJoin(IMessage message, FunctionContext context, String namespace, String nameListName, String intelligenceFieldName, String asName) {
+ intelligenceInner(message, context, namespace, nameListName, intelligenceFieldName, asName, false);
+ }
+
+ public void intelligenceInner(IMessage message, FunctionContext context, String namespace, String nameListName, String intelligenceFieldName, String asName, boolean isInner) {
+ String key = FunctionUtils.getValueString(message, context, intelligenceFieldName);
+ namespace = FunctionUtils.getValueString(message, context, namespace);
+ nameListName = FunctionUtils.getValueString(message, context, nameListName);
+ ConfigurableComponent configurableComponent = ComponentCreator.getComponent(namespace, ConfigurableComponent.class);
+ AbstractIntelligenceCache intelligenceCache = configurableComponent.queryConfigurable(AbstractIntelligenceCache.TYPE, nameListName);
+ if (intelligenceCache == null) {
+ throw new RuntimeException("can not query intelligence. the namespace is " + namespace + ", the name is " + nameListName);
+ }
+ Map<String, Object> row = intelligenceCache.getRow(key);
+ if (row != null) {
+ asName = FunctionUtils.getValueString(message, context, asName);
+ if (StringUtil.isNotEmpty(asName)) {
+ asName = asName + ".";
+ } else {
+ asName = "";
+ }
+ for (Entry<String, Object> entry : row.entrySet()) {
+ String elementKey = asName + entry.getKey();
+ message.getMessageBody().put(elementKey, entry.getValue());
+ }
+ } else {
+ if (isInner) {
+ context.breakExecute();
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/function/script/IntelligenceNameListFunction.java b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/function/script/IntelligenceNameListFunction.java
new file mode 100644
index 0000000..02d67fb
--- /dev/null
+++ b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/function/script/IntelligenceNameListFunction.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.dim.function.script;
+
+import org.apache.rocketmq.streams.script.annotation.Function;
+
+@Function
+public class IntelligenceNameListFunction {
+
+}
diff --git a/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/function/script/NameListFunction.java b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/function/script/NameListFunction.java
new file mode 100644
index 0000000..3b03a4e
--- /dev/null
+++ b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/function/script/NameListFunction.java
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.dim.function.script;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+
+import org.apache.rocketmq.streams.common.component.ComponentCreator;
+import org.apache.rocketmq.streams.dim.DimComponent;
+import org.apache.rocketmq.streams.script.context.FunctionContext;
+import org.apache.rocketmq.streams.script.annotation.Function;
+import org.apache.rocketmq.streams.script.annotation.FunctionMethod;
+import org.apache.rocketmq.streams.common.context.IMessage;
+import org.apache.rocketmq.streams.script.utils.FunctionUtils;
+import org.apache.rocketmq.streams.common.utils.StringUtil;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+@Function
+public class NameListFunction {
+
+ private static final Log LOG = LogFactory.getLog(NameListFunction.class);
+
+ private DimComponent nameListComponent;
+
+ @FunctionMethod(value = "innerJoin", alias = "inner_join")
+ public JSONArray innerJoin(IMessage message, FunctionContext context, String namespace, String nameListName,
+ String expressionStr, String alias, String script, String... fieldNames) {
+ JSONArray rows = getRows(message, context, namespace, nameListName, expressionStr, alias, script, fieldNames);
+ if (rows == null || rows.size() == 0) {
+ context.breakExecute();
+ return null;
+ }
+ return rows;
+ }
+
+ @FunctionMethod(value = "leftJoin", alias = "left_join")
+ public JSONArray leftJoin(IMessage message, FunctionContext context, String namespace, String nameListName,
+ String expressionStr, String alias, String script, String... fieldNames) {
+ JSONArray rows = getRows(message, context, namespace, nameListName, expressionStr, alias, script, fieldNames);
+ if (rows == null || rows.size() == 0) {
+ return null;
+ }
+ return rows;
+ }
+
+ @FunctionMethod(value = "mark_rows", alias = "namelist_rows")
+ public String namelist(IMessage message, FunctionContext context, String namespace, String nameListName,
+ String expressionStr, String... fieldNames) {
+ JSONArray rows = getRows(message, context, namespace, nameListName, expressionStr, null, null, fieldNames);
+ if (rows == null || rows.size() == 0) {
+ return null;
+ }
+ return rows.toJSONString();
+ }
+
+ @FunctionMethod(value = "mark", alias = "namelist")
+ public String namelist(IMessage message, FunctionContext context, String namespace, String nameListName,
+ String expressionStr, String fieldName) {
+ String tmp = fieldName;
+ nameListName = FunctionUtils.getValueString(message, context, nameListName);
+ namespace = FunctionUtils.getValueString(message, context, namespace);
+ expressionStr = FunctionUtils.getValueString(message, context, expressionStr);
+ fieldName = FunctionUtils.getValueString(message, context, fieldName);
+ if (fieldName == null) {
+ fieldName = tmp;
+ }
+ nameListComponent = ComponentCreator.getComponent(namespace, DimComponent.class);
+ Map<String, Object> row =
+ nameListComponent.getService().match(nameListName, expressionStr, message.getMessageBody());
+ if (row != null) {
+ Object value = row.get(fieldName);
+ if (value == null) {
+ return null;
+ }
+ return value.toString();
+ }
+ return null;
+ }
+
+ @FunctionMethod(value = "mark", alias = "namelist")
+ public String namelist(IMessage message, FunctionContext context, String namespace, String nameListName,
+ String expressionStr, String fieldNames, String joinMark) {
+ nameListName = FunctionUtils.getValueString(message, context, nameListName);
+ namespace = FunctionUtils.getValueString(message, context, namespace);
+ expressionStr = FunctionUtils.getValueString(message, context, expressionStr);
+ fieldNames = FunctionUtils.getValueString(message, context, fieldNames);
+ joinMark = FunctionUtils.getValueString(message, context, joinMark);
+ nameListComponent = ComponentCreator.getComponent(namespace, DimComponent.class);
+ Map<String, Object> row =
+ nameListComponent.getService().match(nameListName, expressionStr, message.getMessageBody());
+ if (row != null) {
+ String[] fieldNameTem = fieldNames.split(",");
+ StringBuilder result = new StringBuilder();
+ for (int i = 0; i < fieldNameTem.length; i++) {
+ Object tem = row.get(fieldNameTem[i]);
+ if (tem != null) {
+ if (result.length() == 0) {
+ if (StringUtil.isNotEmpty(tem.toString()) && !("null".equalsIgnoreCase(tem.toString()))) {
+ result.append(tem);
+ }
+ } else {
+ if (StringUtil.isNotEmpty(tem.toString()) && !("null".equalsIgnoreCase(tem.toString()))) {
+ result.append(joinMark + tem);
+ }
+ }
+ }
+
+ }
+ return result.toString();
+ }
+ return null;
+ }
+
+ @FunctionMethod(value = "in_namelist", alias = "in_namelist")
+ public Boolean inNameList(IMessage message, FunctionContext context, String namespace, String nameListName,
+ String expressionStr) {
+ nameListComponent = ComponentCreator.getComponent(namespace, DimComponent.class);
+ Map<String, Object> row =
+ nameListComponent.getService().match(nameListName, expressionStr, message.getMessageBody());
+ if (row != null && row.size() > 0) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 根据表达式,从namelist中获取符合条件的数据
+ *
+ * @param message
+ * @param context
+ * @param namespace
+ * @param nameListName
+ * @param expressionStr (varname,functionName,value)&(varname,functionName,value)
+ * @param fieldNames 需要返回的字段名
+ * @return
+ */
+ protected JSONArray getRows(IMessage message, FunctionContext context, String namespace, String nameListName, String expressionStr, String alias, String script, String... fieldNames) {
+ nameListName = FunctionUtils.getValueString(message, context, nameListName);
+ namespace = FunctionUtils.getValueString(message, context, namespace);
+ expressionStr = FunctionUtils.getValueString(message, context, expressionStr);
+ script = FunctionUtils.getValueString(message, context, script);
+ if (StringUtil.isEmpty(script)) {
+ script = null;
+ }
+ nameListComponent = ComponentCreator.getComponent(namespace, DimComponent.class);
+ List<Map<String, Object>> rows =
+ nameListComponent.getService().matchSupportMultiRow(nameListName, expressionStr, message.getMessageBody(), script);
+ if (rows == null || rows.size() == 0) {
+ return null;
+ }
+ JSONArray jsonArray = new JSONArray();
+ for (Map<String, Object> row : rows) {
+ JSONObject jsonObject = new JSONObject();
+ if (fieldNames == null || fieldNames.length == 0) {
+ if (alias == null) {
+ jsonObject.putAll(row);
+ } else {
+ Iterator<Entry<String, Object>> it = row.entrySet().iterator();
+ while (it.hasNext()) {
+ Entry<String, Object> entry = it.next();
+ String fieldName = entry.getKey();
+ if (alias != null) {
+ fieldName = alias + "." + fieldName;
+ }
+ jsonObject.put(fieldName, entry.getValue());
+ }
+ }
+
+ } else {
+ for (String fieldName : fieldNames) {
+ String tmp = fieldName;
+ if (alias != null) {
+ fieldName = alias + "." + fieldName;
+ }
+ jsonObject.put(fieldName, row.get(tmp));
+ }
+ }
+ jsonArray.add(jsonObject);
+ }
+ return jsonArray;
+ }
+
+}
\ No newline at end of file
diff --git a/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/index/DimIndex.java b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/index/DimIndex.java
new file mode 100644
index 0000000..6c6aeef
--- /dev/null
+++ b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/index/DimIndex.java
@@ -0,0 +1,319 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.dim.index;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.rocketmq.streams.common.datatype.IntDataType;
+import org.apache.rocketmq.streams.common.cache.compress.impl.IntValueKV;
+import org.apache.rocketmq.streams.common.cache.CompressTable;
+import org.apache.rocketmq.streams.common.utils.MapKeyUtil;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+public class DimIndex {
+
+ private static final Log LOG = LogFactory.getLog(DimIndex.class);
+
+ /**
+ * 索引字段名,支持多个索引,每个索引一行,支持组合索引,多个字段用;拼接 name 单索引 name;age 组合索引
+ */
+ protected List<String> indexs = new ArrayList<>();
+
+ /**
+ * 如果是唯一索引,用压缩值存储 每个索引一行
+ */
+ protected Map<String, IntValueKV> uniqueIndex;
+
+ /**
+ * 如果是非唯一索引,用这个结构存储 每个索引一行,后面的map:key:索引值;value:row id 列表,rowid用字节表示
+ */
+ protected Map<String, Map<String, List<Integer>>> mutilIndex = new HashMap<>();
+
+ protected boolean isUnique = false;//如果是唯一索引,值为true
+
+ public DimIndex(List<String> indexs) {
+ this.indexs = formatIndexs(indexs);
+ }
+
+ public DimIndex(String index, String... indexs) {
+ if (indexs == null) {
+ return;
+ }
+ List<String> indexList = new ArrayList<>();
+ for (String idx : indexs) {
+ indexList.add(idx);
+ }
+ this.indexs = formatIndexs(indexList);
+ }
+
+ /**
+ * 组合索引,多个字段要以名称顺序排列,完成索引名称的标注化处理
+ *
+ * @param indexs
+ * @return
+ */
+ protected List<String> formatIndexs(List<String> indexs) {
+ List<String> allIndex = new ArrayList<>();
+ for (String indexName : indexs) {
+ String[] values = indexName.split(";");
+ List<String> indexList = new ArrayList<>();
+ for (String value : values) {
+ indexList.add(value);
+ }
+ Collections.sort(indexList);
+ String indexKey = MapKeyUtil.createKey(indexList);
+ allIndex.add(indexKey);
+ }
+ return allIndex;
+ }
+
+ /**
+ * 加载一行数据,如果是唯一索引,在uniqueIndex中查找,否则在mutilIndex查找
+ *
+ * @param indexName 索引名,如name
+ * @param indexValue 索引值,如chris
+ * @return
+ */
+ public List<Integer> getRowIds(String indexName, String indexValue) {
+ if (isUnique) {
+ IntValueKV index = this.uniqueIndex.get(indexName);
+ if (index == null) {
+ return null;
+ }
+ Integer rowId = index.get(indexValue);
+ if (rowId == null) {
+ return null;
+ }
+ List<Integer> rowIds = new ArrayList<>();
+ rowIds.add(rowId);
+ return rowIds;
+ } else {
+ Map<String, List<Integer>> indexs = this.mutilIndex.get(indexName);
+ if (indexs == null) {
+ return null;
+ }
+ return indexs.get(indexValue);
+ }
+ }
+
+ /**
+ * 构建索引,如果是唯一索引,构建在uniqueIndex数据结构中,否则构建在mutilIndex这个数据结构中
+ *
+ * @param tableCompress 表数据
+ */
+ public void buildIndex(CompressTable tableCompress) {
+
+ if (isUnique) {
+ Map<String, IntValueKV> fieldIndex2RowIndex = new HashMap<>();
+ buildUniqueIndex(tableCompress, fieldIndex2RowIndex);
+ this.uniqueIndex = fieldIndex2RowIndex;
+ } else {
+ Map<String, Map<String, List<Integer>>> fieldIndex2RowIndex = new HashMap<>();
+ buildIndex(tableCompress, fieldIndex2RowIndex);
+ this.mutilIndex = fieldIndex2RowIndex;
+ }
+ }
+
+ /**
+ * 构建唯一索引
+ *
+ * @param tableCompress
+ * @param fieldIndex2RowIndex
+ */
+ protected void buildUniqueIndex(CompressTable tableCompress, Map<String, IntValueKV> fieldIndex2RowIndex) {
+ if (indexs == null || indexs.size() == 0) {
+ return;
+ }
+
+ int i = 0;
+ Iterator<Map<String, Object>> it = tableCompress.newIterator();
+ while (it.hasNext()) {
+ /**
+ * 为每个索引做构建
+ */
+ for (String indexName : indexs) {
+ IntValueKV index = fieldIndex2RowIndex.get(indexName);
+ if (index == null) {
+ synchronized (this) {
+ index = fieldIndex2RowIndex.get(indexName);
+ if (index == null) {
+ index = new IntValueKV(tableCompress.getRowCount());
+ fieldIndex2RowIndex.put(indexName, index);
+ }
+ }
+
+ }
+ String[] nameIndexs = indexName.split(";");
+ Arrays.sort(nameIndexs);
+ Map<String, String> cacheValues = createRow(it.next());
+ String indexValue = createIndexValue(cacheValues, nameIndexs);
+ index.put(indexValue, i);
+ }
+ i++;
+ }
+ LOG.info(" finish poll data , the row count is " + i + ". byte is " + tableCompress
+ .getByteCount());
+ }
+
+ /**
+ * 从table compress 中取出所有的行,构建索引。把
+ *
+ * @param dataCacheVar
+ * @param fieldIndex2RowIndex
+ */
+ protected void buildIndex(CompressTable dataCacheVar, Map<String, Map<String, List<Integer>>> fieldIndex2RowIndex) {
+ Iterator<Map<String, Object>> it = dataCacheVar.newIterator();
+ int i = 0;
+ while (it.hasNext()) {
+ Map<String, Object> row = it.next();
+ Map<String, String> cacheValues = createRow(row);
+ putValue2Index(cacheValues, i, fieldIndex2RowIndex);
+ i++;
+ }
+
+ LOG.info(" finish poll data , the row count is " + i + ". byte is " + dataCacheVar
+ .getByteCount());
+
+ }
+
+ /**
+ * 创建索引结构
+ *
+ * @param value
+ * @param rowIndex
+ * @param fieldIndex2RowIndex
+ */
+ protected void putValue2Index(Map<String, String> value, Integer rowIndex,
+ Map<String, Map<String, List<Integer>>> fieldIndex2RowIndex) {
+ if (indexs == null || indexs.size() == 0) {
+ return;
+ }
+ for (String indexName : indexs) {
+ Map<String, List<Integer>> name2RowIndexs = fieldIndex2RowIndex.get(indexName);
+ if (name2RowIndexs == null) {
+ synchronized (this) {
+ name2RowIndexs = fieldIndex2RowIndex.get(indexName);
+ if (name2RowIndexs == null) {
+ name2RowIndexs = new HashMap<>();
+ fieldIndex2RowIndex.put(indexName, name2RowIndexs);
+ }
+ }
+
+ }
+ String[] nameIndexs = indexName.split(";");
+ Arrays.sort(nameIndexs);
+ addValue2Index(indexName, name2RowIndexs, rowIndex, value, nameIndexs);
+ }
+ }
+
+ /**
+ * 根据索引名称,把不同的索引值创建key,value放入索引缓存
+ *
+ * @param indexName 索引名称,多字段索引用;分割
+ * @param name2RowIndexs 索引值,list<id>
+ * @param rowIndex 行号
+ * @param row
+ */
+ protected void addValue2Index(String indexName, Map<String, List<Integer>> name2RowIndexs, Integer rowIndex,
+ Map<String, String> row, String[] nameIndexs) {
+ String indexValue = createIndexValue(row, nameIndexs);
+ addValue2Index(name2RowIndexs, rowIndex, indexValue);
+ }
+
+ /**
+ * 对于组合索引,把各个字段的值取出来
+ *
+ * @param row
+ * @param nameIndexs
+ * @return
+ */
+ protected String createIndexValue(Map<String, String> row, String[] nameIndexs) {
+ String[] indexValues = new String[nameIndexs.length];
+ for (int i = 0; i < nameIndexs.length; i++) {
+ indexValues[i] = row.get(nameIndexs[i]);
+ }
+ if (indexValues != null && indexValues.length > 0) {
+ String indexValue = MapKeyUtil.createKey(indexValues);
+ return indexValue;
+ }
+ return null;
+ }
+
+ /**
+ * 把row 中非string的值转化成string
+ *
+ * @param row
+ * @return
+ */
+ protected Map<String, String> createRow(Map<String, Object> row) {
+ Map<String, String> cacheValues = new HashMap<String, String>();//一行数据
+ Iterator<Map.Entry<String, Object>> iterator = row.entrySet().iterator();
+ //把数据value从object转化成string
+ while (iterator.hasNext()) {
+ Map.Entry<String, Object> entry = iterator.next();
+ if (entry != null && entry.getValue() != null && entry.getKey() != null) {
+ cacheValues.put(entry.getKey(), entry.getValue().toString());
+ }
+ }
+ return cacheValues;
+ }
+
+ public static IntDataType INTDATATYPE = new IntDataType();
+
+ /**
+ * 把确定多索引值,和行号放入索引缓存
+ *
+ * @param name2RowIndexs
+ * @param rowIndex
+ * @param indexValue
+ */
+ protected void addValue2Index(Map<String, List<Integer>> name2RowIndexs, Integer rowIndex,
+ String indexValue) {
+
+ List<Integer> rowIndexs = name2RowIndexs.get(indexValue);
+ if (rowIndexs == null) {
+ rowIndexs = new ArrayList<>();
+ name2RowIndexs.put(indexValue, rowIndexs);
+ }
+ rowIndexs.add(rowIndex);
+
+ }
+
+ public boolean isUnique() {
+ return isUnique;
+ }
+
+ public void setUnique(boolean unique) {
+ isUnique = unique;
+ }
+
+ public List<String> getIndexs() {
+ return indexs;
+ }
+
+ public void setIndexs(List<String> indexs) {
+ this.indexs = indexs;
+ }
+
+}
diff --git a/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/index/IndexExecutor.java b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/index/IndexExecutor.java
new file mode 100644
index 0000000..ceb4f52
--- /dev/null
+++ b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/index/IndexExecutor.java
@@ -0,0 +1,258 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.dim.index;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.alibaba.fastjson.JSONObject;
+
+import org.apache.rocketmq.streams.dim.model.AbstractDim;
+import org.apache.rocketmq.streams.filter.builder.ExpressionBuilder;
+import org.apache.rocketmq.streams.filter.function.expression.Equals;
+import org.apache.rocketmq.streams.filter.operator.Rule;
+import org.apache.rocketmq.streams.filter.operator.expression.Expression;
+import org.apache.rocketmq.streams.filter.operator.expression.RelationExpression;
+import org.apache.rocketmq.streams.script.ScriptComponent;
+import org.apache.rocketmq.streams.common.datatype.IntDataType;
+import org.apache.rocketmq.streams.common.utils.DataTypeUtil;
+import org.apache.rocketmq.streams.common.utils.MapKeyUtil;
+
+/**
+ * 执行索引的查询和构建。主要是完成表达式的解析,对于等值的表达式字段,如果有索引,根据索引查询,然后执行非等值部分的判断
+ */
+public class IndexExecutor {
+
+ private String expressionStr;//表达式
+
+ private boolean isSupport = false;//是否支持索引,比如表达式等值部分无所以,则不能走索引逻辑
+
+ private String namespace;
+
+ private String indexNameKey;//索引的名字,多个字段";"分隔
+
+ private List<String> msgNames;//
+
+ private Rule rule;//表达式会被编译成rule
+
+ private List<String> index; //标准化后的索引name
+
+ private Set<String> indexNames = new HashSet<>();
+
+ public IndexExecutor(String expressionStr, String namespace, List<String> index) {
+ this.expressionStr = expressionStr;
+ this.namespace = namespace;
+
+ List<String> allIndex = new ArrayList<>();
+ for (String indexName : index) {
+ String[] values = indexName.split(";");
+ List<String> indexList = new ArrayList<>();
+ for (String value : values) {
+ indexNames.add(value);
+ indexList.add(value);
+ }
+ Collections.sort(indexList);
+ String indexKey = MapKeyUtil.createKey(indexList);
+ allIndex.add(indexKey);
+ }
+ this.index = allIndex;
+ parse();
+ }
+
+ /**
+ * 解析表达式,找出等值字段和非等值字段 如果有索引走索引,否则走全表扫描
+ */
+ protected void parse() {
+ List<Expression> expressions = new ArrayList<>();
+ List<RelationExpression> relationExpressions = new ArrayList<>();
+ Expression expression = ExpressionBuilder.createOptimizationExpression(namespace, "tmp", expressionStr,
+ expressions, relationExpressions);
+
+ RelationExpression relationExpression = null;
+ if (RelationExpression.class.isInstance(expression)) {
+ relationExpression = (RelationExpression)expression;
+ if (!"and".equals(relationExpression.getRelation())) {
+ isSupport = false;
+ return;
+ }
+ }
+
+ this.isSupport = true;
+ List<Expression> indexExpressions = new ArrayList<>();
+ List<Expression> otherExpressions = new ArrayList<>();
+ if (relationExpression != null) {
+ Map<String, Expression> map = new HashMap<>();
+ for (Expression tmp : expressions) {
+ map.put(tmp.getConfigureName(), tmp);
+ }
+ for (Expression tmp : relationExpressions) {
+ map.put(tmp.getConfigureName(), tmp);
+ }
+ List<String> expressionNames = relationExpression.getValue();
+ relationExpression.setValue(new ArrayList<>());
+ for (String expressionName : expressionNames) {
+ Expression subExpression = map.get(expressionName);
+ if (subExpression != null && !RelationExpression.class.isInstance(subExpression)) {
+ indexExpressions.add(subExpression);
+ } else {
+ otherExpressions.add(subExpression);
+ relationExpression.getValue().add(subExpression.getConfigureName());
+ }
+ }
+
+ } else {
+ indexExpressions.add(expression);
+ }
+
+ List<String> fieldNames = new ArrayList<>();
+ Map<String, String> msgNames = new HashMap<>();
+
+ for (Expression expre : indexExpressions) {
+ if (RelationExpression.class.isInstance(expre)) {
+ continue;
+ }
+ String indexName = expre.getValue().toString();
+ if (Equals.isEqualFunction(expre.getFunctionName()) && indexNames.contains(indexName)) {
+
+ fieldNames.add(indexName);
+ msgNames.put(indexName, expre.getVarName());
+ }
+ }
+ Collections.sort(fieldNames);
+ indexNameKey = MapKeyUtil.createKey(fieldNames);
+ if (!this.index.contains(indexNameKey)) {
+ this.isSupport = false;
+ return;
+ }
+ this.msgNames = createMsgNames(fieldNames, msgNames);
+ if (otherExpressions.size() == 0) {
+ return;
+ }
+ Rule rule = null;
+ if (relationExpression == null) {
+ rule = ExpressionBuilder.createRule(namespace, "tmpRule", expression);
+ } else {
+ rule = ExpressionBuilder.createRule(namespace, "tmpRule", expression, expressions, relationExpressions);
+ }
+
+ this.rule = rule;
+
+ }
+
+ /**
+ * 创建索引字段的索引值
+ *
+ * @param fieldNames
+ * @param msgNames
+ * @return
+ */
+ protected List<String> createMsgNames(List<String> fieldNames, Map<String, String> msgNames) {
+ List<String> msgNameList = new ArrayList<>();
+ for (String fieldName : fieldNames) {
+ msgNameList.add(msgNames.get(fieldName));
+ }
+ return msgNameList;
+ }
+
+ public boolean isSupport() {
+ return isSupport;
+ }
+
+ private static IntDataType intDataType = new IntDataType();
+
+ public List<Map<String, Object>> match(JSONObject msg, AbstractDim nameList, boolean needAll) {
+ return match(msg, nameList, needAll);
+ }
+
+ public List<Map<String, Object>> match(JSONObject msg, AbstractDim nameList, boolean needAll, String script) {
+ List<Map<String, Object>> rows = new ArrayList<>();
+ String msgValue = createValue(msg);
+ List<Integer> rowIds = nameList.findRowIdByIndex(indexNameKey, msgValue);
+ if (rowIds == null) {
+ return null;
+ }
+ for (Integer rowId : rowIds) {
+ Map<String, Object> oldRow = nameList.getDataCache().getRow(rowId);
+ Map<String, Object> newRow = executeScript(oldRow, script);
+ if (rule == null) {
+ rows.add(newRow);
+ if (needAll == false) {
+ return rows;
+ }
+ continue;
+ }
+ Rule ruleTemplete = this.rule;
+ Rule rule = ruleTemplete.copy();
+ Map<String, Expression> expressionMap = new HashMap<>();
+ for (Expression expression : rule.getExpressionMap().values()) {
+ expressionMap.put(expression.getConfigureName(), expression);
+ if (RelationExpression.class.isInstance(expression)) {
+ continue;
+ }
+ Object object = expression.getValue();
+ if (object != null && DataTypeUtil.isString(object.getClass())) {
+ String fieldName = (String)object;
+ Object o = newRow.get(fieldName);
+ if (o != null) {
+ Expression e = expression.copy();
+ e.setValue(o.toString());
+ expressionMap.put(e.getConfigureName(), e);
+ }
+ }
+ }
+ rule.setExpressionMap(expressionMap);
+ boolean matched = rule.execute(msg);
+ if (matched) {
+ rows.add(newRow);
+ if (needAll == false) {
+ return rows;
+ }
+ }
+ }
+ return rows;
+ }
+
+ protected Map<String, Object> executeScript(Map<String, Object> oldRow, String script) {
+ if (script == null) {
+ return oldRow;
+ }
+ ScriptComponent scriptComponent = ScriptComponent.getInstance();
+ JSONObject msg = new JSONObject();
+ msg.putAll(oldRow);
+ scriptComponent.getService().executeScript(msg, script);
+ return msg;
+ }
+
+ /**
+ * 按顺序创建msg的key
+ *
+ * @param msg
+ * @return
+ */
+ private String createValue(JSONObject msg) {
+ List<String> value = new ArrayList<>();
+ for (String msgName : msgNames) {
+ value.add(msg.getString(msgName));
+ }
+ return MapKeyUtil.createKey(value);
+ }
+}
diff --git a/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/intelligence/AbstractIntelligenceCache.java b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/intelligence/AbstractIntelligenceCache.java
new file mode 100644
index 0000000..65b27f7
--- /dev/null
+++ b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/intelligence/AbstractIntelligenceCache.java
@@ -0,0 +1,395 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.dim.intelligence;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+
+import org.apache.rocketmq.streams.http.source.util.HttpUtil;
+import org.apache.rocketmq.streams.common.component.ComponentCreator;
+import org.apache.rocketmq.streams.common.configurable.BasedConfigurable;
+import org.apache.rocketmq.streams.common.configurable.IAfterConfiguableRefreshListerner;
+import org.apache.rocketmq.streams.common.configurable.IConfigurableService;
+import org.apache.rocketmq.streams.common.configurable.annotation.ENVDependence;
+import org.apache.rocketmq.streams.common.configure.ConfigureFileKey;
+import org.apache.rocketmq.streams.common.cache.compress.impl.IntValueKV;
+import org.apache.rocketmq.streams.common.channel.sink.ISink;
+import org.apache.rocketmq.streams.common.utils.NumberUtils;
+import org.apache.rocketmq.streams.common.utils.SQLUtil;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.rocketmq.streams.common.dboperator.IDBDriver;
+import org.apache.rocketmq.streams.db.driver.JDBCDriver;
+
+public abstract class AbstractIntelligenceCache extends BasedConfigurable implements
+ IAfterConfiguableRefreshListerner {
+
+ private static final Log LOG = LogFactory.getLog(AbstractIntelligenceCache.class);
+
+ public static final String TYPE = "intelligence";
+
+ protected static final int FILE_MAX_LINE = 50000;//每个文件最大行数
+
+ protected transient IntValueKV intValueKV = new IntValueKV(0) {
+ @Override
+ public Integer get(String key) {
+ return null;
+ }
+
+ @Override
+ public void put(String key, Integer value) {
+
+ }
+ };
+
+ protected String idFieldName = "id";//必须有id字段
+
+ protected int batchSize = 3000;
+
+ @ENVDependence
+ protected Long pollingTimeMintue = 30L;
+
+ protected String datasourceName;//情报对应的存储
+
+ protected transient IDBDriver outputDataSource;
+
+ protected static ExecutorService executorService;
+
+ protected transient ScheduledExecutorService scheduledExecutorService;
+
+ public AbstractIntelligenceCache() {
+ setType(TYPE);
+ executorService = new ThreadPoolExecutor(20, 20,
+ 0L, TimeUnit.MILLISECONDS,
+ new LinkedBlockingQueue<Runnable>(1000));
+ scheduledExecutorService = new ScheduledThreadPoolExecutor(3);
+ }
+
+ public IntValueKV startLoadData(String sql, IDBDriver resource) {
+ try {
+ String statisticalSQL = sql;
+ int startIndex = sql.toLowerCase().indexOf("from");
+ statisticalSQL = "select count(1) as c, min(" + idFieldName + ") as min, max(" + idFieldName + ") as max "
+ + sql.substring(startIndex);
+ List<Map<String, Object>> rows = resource.queryForList(statisticalSQL);
+ Map<String, Object> row = rows.get(0);
+ int count = Integer.valueOf(row.get("c").toString());
+ IntValueKV intValueKV = new IntValueKV(count);
+ //int maxBatch=count/maxSyncCount;//每1w条数据,一个并发。如果数据量比较大,为了提高性能,并行执行
+ if (count == 0) {
+ return new IntValueKV(0) {
+ @Override
+ public Integer get(String key) {
+ return null;
+ }
+
+ @Override
+ public void put(String key, Integer value) {
+
+ }
+ };
+ }
+ long min = Long.valueOf(row.get("min").toString());
+ long max = Long.valueOf(row.get("max").toString());
+ int maxSyncCount = count / FILE_MAX_LINE + 1;
+ long step = (max - min + 1) / maxSyncCount;
+ CountDownLatch countDownLatch = new CountDownLatch(maxSyncCount + 1);
+ AtomicInteger finishedCount = new AtomicInteger(0);
+ String taskSQL = null;
+ if (sql.indexOf(" where ") != -1) {
+ taskSQL = sql + " and " + idFieldName + ">#{startIndex} and " + idFieldName + "<=#{endIndex} order by "
+ + idFieldName + " limit " + batchSize;
+ } else {
+ taskSQL = sql + " where " + idFieldName + ">#{startIndex} and " + idFieldName
+ + "<=#{endIndex} order by " + idFieldName + " limit " + batchSize;
+ }
+
+ int i = 0;
+ for (; i < maxSyncCount; i++) {
+ FetchDataTask fetchDataTask = new FetchDataTask(taskSQL, (min - 1) + step * i,
+ (min - 1) + step * (i + 1), countDownLatch, finishedCount, resource, i, intValueKV, this, count);
+ executorService.execute(fetchDataTask);
+ }
+ FetchDataTask fetchDataTask = new FetchDataTask(taskSQL, (min - 1) + step * i, (min - 1) + step * (i + 1),
+ countDownLatch, finishedCount, resource, i, intValueKV, this, count);
+ executorService.execute(fetchDataTask);
+
+ countDownLatch.await();
+
+ LOG.info(getClass().getSimpleName() + " load data finish, load data line size is " + intValueKV.getSize());
+ return intValueKV;
+ } catch (Exception e) {
+ LOG.error("failed loading intelligence data!", e);
+ return new IntValueKV(0) {
+ @Override
+ public Integer get(String key) {
+ return null;
+ }
+
+ @Override
+ public void put(String key, Integer value) {
+
+ }
+ };
+ }
+ }
+
+ protected transient AtomicBoolean hasInit = new AtomicBoolean(false);
+
+ @Override
+ public void doProcessAfterRefreshConfigurable(IConfigurableService configurableService) {
+ this.outputDataSource = configurableService.queryConfigurable(ISink.TYPE, datasourceName);
+ }
+
+ public void startIntelligence() {
+ boolean success = dbInit();
+ if (success) {
+ startIntelligenceInner();
+ } else {
+ Thread thread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ boolean success = false;
+ while (!success) {
+ success = dbInit();
+ try {
+ Thread.sleep(60 * 1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ startIntelligenceInner();
+ }
+ });
+ thread.start();
+ }
+ }
+
+ public void startIntelligenceInner() {
+ String sql = getSQL();
+ if (hasInit.compareAndSet(false, true)) {
+ this.intValueKV = startLoadData(sql, outputDataSource);
+ scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
+ @Override
+ public void run() {
+ intValueKV = startLoadData(sql, outputDataSource);
+ }
+ }, pollingTimeMintue, pollingTimeMintue, TimeUnit.MINUTES);
+ }
+ }
+
+ public abstract Map<String, Object> getRow(String key);
+
+ /**
+ * 查询情报需要的sql
+ *
+ * @return
+ */
+ protected abstract String getSQL();
+
+ /**
+ * 情报中的 情报字段名
+ *
+ * @return
+ */
+ public abstract String getKeyName();
+
+ /**
+ * 情报对应的表名
+ *
+ * @return
+ */
+ public abstract String getTableName();
+
+ protected class FetchDataTask implements Runnable {
+ IntValueKV intValueKV;
+ long startIndex;
+ long endIndex;
+ String sql;
+ CountDownLatch countDownLatch;
+ int index;
+ IDBDriver resource;
+ AtomicInteger finishedCount;//完成了多少条
+ AbstractIntelligenceCache cache;
+ int totalSize;//一共有多少条数据
+
+ public FetchDataTask(String sql, long startIndex, long endIndex, CountDownLatch countDownLatch,
+ AtomicInteger finishedCount, IDBDriver resource, int i, IntValueKV intValueKV, AbstractIntelligenceCache cache, int totalSize) {
+ this.startIndex = startIndex;
+ this.endIndex = endIndex;
+ this.countDownLatch = countDownLatch;
+ this.sql = sql;
+ this.finishedCount = finishedCount;
+ this.resource = resource;
+ this.index = i;
+ this.intValueKV = intValueKV;
+ this.cache = cache;
+ this.totalSize = totalSize;
+ }
+
+ @Override
+ public void run() {
+ long currentIndex = startIndex;
+ JSONObject msg = new JSONObject();
+ msg.put("endIndex", endIndex);
+ while (true) {
+ try {
+
+ msg.put("startIndex", currentIndex);
+
+ String sql = SQLUtil.parseIbatisSQL(msg, this.sql);
+ List<Map<String, Object>> rows = resource.queryForList(sql);
+ if (rows == null || rows.size() == 0) {
+ break;
+ }
+ currentIndex = Long.valueOf(rows.get(rows.size() - 1).get(idFieldName).toString());
+
+ int size = rows.size();
+ int count = finishedCount.addAndGet(size);
+ double progress = (double)count / (double)totalSize;
+ progress = progress * 100;
+ System.out.println(cache.getClass().getSimpleName() + ", finished count is " + count + " the total count is " + totalSize + ", the progress is " + String.format("%.2f", progress) + "%");
+ if (size < batchSize) {
+ if (size > 0) {
+ doProccRows(intValueKV, rows, index);
+ }
+ break;
+ }
+ doProccRows(intValueKV, rows, index);
+ } catch (Exception e) {
+ throw new RuntimeException("put data error ", e);
+ }
+ }
+
+ countDownLatch.countDown();
+ }
+ }
+
+ public boolean dbInit() {
+ String uri = "/api/rest/rds/getDataSource";
+ String httpPrefix = "http";
+ String httpProtocol = "http://";
+ try {
+ String dbEndpoint = ComponentCreator.getProperties().getProperty(
+ ConfigureFileKey.INTELLIGENCE_TIP_DB_ENDPOINT);
+ if (StringUtils.isNotBlank(dbEndpoint)) {
+ if (!dbEndpoint.startsWith(httpPrefix)) {
+ dbEndpoint = httpProtocol + dbEndpoint;
+ }
+ String content = HttpUtil.getContent(dbEndpoint + uri);
+ if (StringUtils.isNotBlank(content)) {
+ JSONObject obj = JSON.parseObject(content);
+ String statusCode = "code";
+ int successCode = 200;
+ if (obj.getInteger(statusCode) == successCode) {
+ JSONObject data = obj.getJSONObject("data");
+ JSONObject dbInfo = data.getJSONObject("dBInfo");
+ if (dbInfo != null) {
+ String dbUrl = "jdbc:mysql://" + dbInfo.getString("dbConnection") + ":" + dbInfo.getInteger(
+ "port") + "/" + dbInfo.getString("dBName");
+ String dbUserName = dbInfo.getString("userName");
+ String dbPassword = dbInfo.getString("passWord");
+ JDBCDriver dataSource = (JDBCDriver)this.outputDataSource;
+ dataSource.setUrl(dbUrl);
+ dataSource.setPassword(dbPassword);
+ dataSource.setUserName(dbUserName);
+ dataSource.setHasInit(false);
+ dataSource.init();
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ /**
+ * 把存储0/1字符串的值,转化成bit
+ *
+ * @param values
+ * @return
+ */
+ protected int createInt(List<String> values) {
+ return NumberUtils.createBitMapInt(values);
+ }
+
+ /**
+ * 获取某位的值,如果是1,返回字符串1,否则返回null
+ *
+ * @param num
+ * @param i
+ * @return
+ */
+ protected String getNumBitValue(int num, int i) {
+ boolean exist = NumberUtils.getNumFromBitMapInt(num, i);
+ if (exist) {
+ return "1";
+ }
+ return null;
+ }
+
+ protected abstract void doProccRows(IntValueKV intValueKV, List<Map<String, Object>> rows, int index);
+
+ public String getIdFieldName() {
+ return idFieldName;
+ }
+
+ public void setIdFieldName(String idFieldName) {
+ this.idFieldName = idFieldName;
+ }
+
+ public int getBatchSize() {
+ return batchSize;
+ }
+
+ public void setBatchSize(int batchSize) {
+ this.batchSize = batchSize;
+ }
+
+ public Long getPollingTimeMintue() {
+ return pollingTimeMintue;
+ }
+
+ public void setPollingTimeMintue(Long pollingTimeMintue) {
+ this.pollingTimeMintue = pollingTimeMintue;
+ }
+
+ public String getDatasourceName() {
+ return datasourceName;
+ }
+
+ public void setDatasourceName(String datasourceName) {
+ this.datasourceName = datasourceName;
+ }
+}
diff --git a/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/intelligence/AccountIntelligenceCache.java b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/intelligence/AccountIntelligenceCache.java
new file mode 100644
index 0000000..9466f3e
--- /dev/null
+++ b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/intelligence/AccountIntelligenceCache.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.dim.intelligence;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.rocketmq.streams.common.configurable.IAfterConfiguableRefreshListerner;
+import org.apache.rocketmq.streams.common.cache.compress.impl.IntValueKV;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * table: ads_yunsec_abnormal_account
+ */
+public class AccountIntelligenceCache extends AbstractIntelligenceCache implements IAfterConfiguableRefreshListerner {
+
+ private static final Log LOG = LogFactory.getLog(AccountIntelligenceCache.class);
+
+ /**
+ * 情报域名
+ */
+ protected transient String keyName = "account";
+
+ @Override
+ public Map<String, Object> getRow(String account) {
+ Integer value = intValueKV.get(account);
+ if (value == null) {
+ return null;
+ }
+ Map<String, Object> row = new HashMap<String, Object>() {{
+ put("account", account);
+ }};
+ return row;
+ }
+
+ @Override
+ protected String getSQL() {
+ return "SELECT id, `account` FROM `ads_yunsec_abnormal_account`";
+ }
+
+ @Override
+ public String getKeyName() {
+ return this.keyName;
+ }
+
+ @Override
+ public String getTableName() {
+ return "ads_yunsec_abnormal_account";
+ }
+
+ @Override
+ protected void doProccRows(IntValueKV intValueKV, List<Map<String, Object>> rows, int index) {
+ rows.forEach(row -> {
+ String account = (String)row.get(keyName);
+ if (account != null) {
+ intValueKV.put(account, 1);
+ }
+ });
+ }
+
+}
diff --git a/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/intelligence/DomainIntelligenceCache.java b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/intelligence/DomainIntelligenceCache.java
new file mode 100644
index 0000000..72a88bf
--- /dev/null
+++ b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/intelligence/DomainIntelligenceCache.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.dim.intelligence;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.rocketmq.streams.common.configurable.IAfterConfiguableRefreshListerner;
+import org.apache.rocketmq.streams.common.cache.compress.impl.IntValueKV;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+public class DomainIntelligenceCache extends AbstractIntelligenceCache implements IAfterConfiguableRefreshListerner {
+ private static final Log LOG = LogFactory.getLog(DomainIntelligenceCache.class);
+ protected transient String keyName = "domain";
+
+ @Override
+ protected String getSQL() {
+ return "SELECT id, `domain` , `is_malicious_source` , `is_phishing` , `is_c2` , `is_mining_pool` FROM `ads_yunsec_ti_domain_all_df` where curdate() < date_add(modify_time, interval expire_time day) ";
+ }
+
+ @Override
+ public Map<String, Object> getRow(String ip) {
+ Integer value = intValueKV.get(ip);
+ if (value == null) {
+ return null;
+ }
+ Map<String, Object> row = new HashMap<>();
+ row.put("is_malicious_source", getNumBitValue(value, 0));
+ row.put("is_phishing", getNumBitValue(value, 1));
+ row.put("is_c2", getNumBitValue(value, 2));
+ row.put("is_mining_pool", getNumBitValue(value, 3));
+ return row;
+ }
+
+ @Override
+ public String getKeyName() {
+ return this.keyName;
+ }
+
+ @Override
+ public String getTableName() {
+ return "ads_yunsec_ti_domain_all_df";
+ }
+
+ @Override
+ protected void doProccRows(IntValueKV intValueKV, List<Map<String, Object>> rows, int index) {
+ for (Map<String, Object> row : rows) {
+ String ip = (String)row.get(keyName);
+ if (ip == null) {
+ LOG.warn("load Intelligence exception ,the ip is null");
+ continue;
+ }
+ List<String> values = new ArrayList<>();
+ values.add((String)row.get("is_malicious_source"));
+ values.add((String)row.get("is_phishing"));
+ values.add((String)row.get("is_c2"));
+ values.add((String)row.get("is_mining_pool"));
+ int value = createInt(values);
+ synchronized (this) {
+ intValueKV.put(ip, value);
+ }
+
+ }
+ }
+
+}
diff --git a/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/intelligence/IPIntelligenceCache.java b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/intelligence/IPIntelligenceCache.java
new file mode 100644
index 0000000..6382cd2
--- /dev/null
+++ b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/intelligence/IPIntelligenceCache.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.dim.intelligence;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.rocketmq.streams.common.component.ComponentCreator;
+import org.apache.rocketmq.streams.common.configurable.IAfterConfiguableRefreshListerner;
+import org.apache.rocketmq.streams.common.cache.compress.impl.IntValueKV;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.rocketmq.streams.common.dboperator.IDBDriver;
+import org.apache.rocketmq.streams.db.driver.DriverBuilder;
+
+public class IPIntelligenceCache extends AbstractIntelligenceCache implements IAfterConfiguableRefreshListerner {
+ private static final Log LOG = LogFactory.getLog(IPIntelligenceCache.class);
+ protected transient String keyName = "ip";
+
+
+ @Override
+ protected String getSQL() {
+ return "SELECT id,ip, `is_web_attack` , `is_tor` , `is_proxy` , `is_nat` , `is_mining_pool` , `is_c2` , "
+ + "`is_malicious_source` , `is_3rd` , `is_idc` , `is_malicious_login` FROM `ads_yunsec_ti_ip_all_df` where curdate() < date_add(modify_time, interval expire_time day) ";
+ }
+
+ @Override
+ public String getKeyName() {
+ return this.keyName;
+ }
+
+ @Override
+ public String getTableName() {
+ return "ads_yunsec_ti_ip_all_df";
+ }
+
+ @Override
+ public Map<String, Object> getRow(String ip) {
+
+ Integer value = intValueKV.get(ip);
+ if (value == null) {
+ return null;
+ }
+ Map<String, Object> row = new HashMap<>();
+
+ row.put("is_web_attack", getNumBitValue(value, 0));
+ row.put("is_tor", getNumBitValue(value, 1));
+ row.put("is_proxy", getNumBitValue(value, 2));
+ row.put("is_nat", getNumBitValue(value, 3));
+ row.put("is_mining_pool", getNumBitValue(value, 4));
+ row.put("is_c2", getNumBitValue(value, 5));
+ row.put("is_malicious_source", getNumBitValue(value, 6));
+ row.put("is_3rd", getNumBitValue(value, 7));
+ row.put("is_idc", getNumBitValue(value, 8));
+ row.put("is_malicious_login", getNumBitValue(value, 9));
+ return row;
+ }
+
+ @Override
+ protected void doProccRows(IntValueKV intValueKV, List<Map<String, Object>> rows, int index) {
+ for (Map<String, Object> row : rows) {
+ String ip = (String)row.get(keyName);
+ if (ip == null) {
+ LOG.warn("load Intelligence exception ,the ip is null");
+ continue;
+ }
+ List<String> values = new ArrayList<>();
+ values.add((String)row.get("is_web_attack"));
+ values.add((String)row.get("is_tor"));
+ values.add((String)row.get("is_proxy"));
+ values.add((String)row.get("is_nat"));
+ values.add((String)row.get("is_mining_pool"));
+ values.add((String)row.get("is_c2"));
+ values.add((String)row.get("is_malicious_source"));
+ values.add((String)row.get("is_3rd"));
+ values.add((String)row.get("is_idc"));
+ values.add((String)row.get("is_malicious_login"));
+ int value = createInt(values);
+ synchronized (this) {
+ intValueKV.put(ip, value);
+ }
+ }
+ }
+
+ public static void main(String[] args) {
+ ComponentCreator.setProperties(
+ "/Users/yuanxiaodong/Documents/workdir/档案/阿里安全/专有云/2020/dipper-siem/siem.properties");
+ IPIntelligenceCache ipIntelligenceCache = new IPIntelligenceCache();
+ IDBDriver outputDataSource = DriverBuilder.createDriver();
+ ipIntelligenceCache.startLoadData(ipIntelligenceCache.getSQL(), outputDataSource);
+ }
+}
diff --git a/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/intelligence/URLIntelligenceCache.java b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/intelligence/URLIntelligenceCache.java
new file mode 100644
index 0000000..8910469
--- /dev/null
+++ b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/intelligence/URLIntelligenceCache.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.dim.intelligence;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.rocketmq.streams.common.configurable.IAfterConfiguableRefreshListerner;
+import org.apache.rocketmq.streams.common.cache.compress.impl.IntValueKV;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+public class URLIntelligenceCache extends AbstractIntelligenceCache implements IAfterConfiguableRefreshListerner {
+
+ private static final Log LOG = LogFactory.getLog(URLIntelligenceCache.class);
+
+ protected transient String keyName = "url";
+
+ @Override
+ protected String getSQL() {
+ return "SELECT id, url, `is_malicious_source` FROM `ads_yunsec_ti_url_all_df` where curdate() < date_add(modify_time, interval expire_time day) ";
+ }
+
+ @Override
+ public Map<String, Object> getRow(String ip) {
+ Integer value = intValueKV.get(ip);
+ if (value == null) {
+ return null;
+ }
+ Map<String, Object> row = new HashMap<>();
+
+ row.put("is_malicious_source", getNumBitValue(value, 0));
+ return row;
+ }
+
+ @Override
+ public String getKeyName() {
+ return this.keyName;
+ }
+
+ @Override
+ public String getTableName() {
+ return "ads_yunsec_ti_url_all_df";
+ }
+
+ @Override
+ protected void doProccRows(IntValueKV intValueKV, List<Map<String, Object>> rows, int index) {
+ for (Map<String, Object> row : rows) {
+ String ip = (String)row.get(keyName);
+ if (ip == null) {
+ LOG.warn("load Intelligence exception ,the ip is null");
+ continue;
+ }
+ List<String> values = new ArrayList<>();
+ values.add((String)row.get("is_malicious_source"));
+ int value = createInt(values);
+ synchronized (this) {
+ intValueKV.put(ip, value);
+ }
+
+ }
+ }
+
+}
diff --git a/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/model/AbstractDim.java b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/model/AbstractDim.java
new file mode 100644
index 0000000..c51b0e6
--- /dev/null
+++ b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/model/AbstractDim.java
@@ -0,0 +1,312 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.dim.model;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import com.alibaba.fastjson.JSONObject;
+
+import org.apache.rocketmq.streams.dim.index.IndexExecutor;
+import org.apache.rocketmq.streams.dim.index.DimIndex;
+import org.apache.rocketmq.streams.filter.builder.ExpressionBuilder;
+import org.apache.rocketmq.streams.filter.operator.Rule;
+import org.apache.rocketmq.streams.filter.operator.expression.Expression;
+import org.apache.rocketmq.streams.filter.operator.expression.RelationExpression;
+import org.apache.rocketmq.streams.common.cache.softreference.ICache;
+import org.apache.rocketmq.streams.common.cache.softreference.impl.SoftReferenceCache;
+import org.apache.rocketmq.streams.common.configurable.BasedConfigurable;
+import org.apache.rocketmq.streams.common.cache.CompressTable;
+import org.apache.rocketmq.streams.common.utils.DataTypeUtil;
+import org.apache.rocketmq.streams.common.utils.MapKeyUtil;
+import org.apache.rocketmq.streams.common.utils.StringUtil;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * 这个结构代表一张表 存放表的全部数据和索引
+ */
+public abstract class AbstractDim extends BasedConfigurable {
+
+ private static final Log LOG = LogFactory.getLog(AbstractDim.class);
+
+ public static final String TYPE = "nameList";
+
+ /**
+ * 同步数据的事件间隔,单位是分钟
+ */
+ protected Long pollingTimeMintue = 60L;
+
+ /**
+ * 支持多组索引,如果一个索引是组合索引,需要拼接成一个string,用;分割 建立索引后,会创建索引的数据结构,类似Map<String,List<RowId>,可以快速定位,无索引会全表扫描,不建议使用 如有两组索引:1.name 2. ip;address
+ */
+ protected List<String> indexs = new ArrayList<>();
+
+ /**
+ * 把表数据转化成二进制存储在CompressTable中
+ */
+ protected transient volatile CompressTable dataCache;
+
+ /**
+ * 建立名单的时候,可以指定多组索引,索引的值当作key,row在datacache的index当作value,可以快速匹配索引对应的row key:索引的值 value:row在dataCache的index当作value,可以快速匹配索引对应的row
+ */
+ protected transient DimIndex nameListIndex;
+
+ //是否是唯一索引,唯一索引会用IntValueKV存储,有更高的压缩率
+ protected boolean isUniqueIndex = false;
+
+ //定时加载表数据到内存
+ protected transient ScheduledExecutorService executorService;
+
+ public AbstractDim() {
+ this.setType(TYPE);
+ }
+
+ //protected String index;//只是做标记,为了是简化indexs的赋值
+
+ public String addIndex(String... fieldNames) {
+ return addIndex(this.indexs, fieldNames);
+ }
+
+ @Override
+ protected boolean initConfigurable() {
+ boolean success = super.initConfigurable();
+ loadNameList();
+ executorService = new ScheduledThreadPoolExecutor(3);
+ executorService.scheduleWithFixedDelay(new Runnable() {
+
+ @Override
+ public void run() {
+ loadNameList();
+ }
+ }, pollingTimeMintue, pollingTimeMintue, TimeUnit.MINUTES);
+ return success;
+ }
+
+ /**
+ * 加载维表数据 创建索引
+ */
+ protected void loadNameList() {
+ try {
+ LOG.info(getConfigureName() + " begin polling data");
+ //全表数据
+ CompressTable dataCacheVar = loadData();
+ this.dataCache = dataCacheVar;
+ this.nameListIndex = buildIndex(dataCacheVar);
+ } catch (Exception e) {
+ LOG.error("Load configurables error:" + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 给维表生成索引数据结构
+ *
+ * @param dataCacheVar 维表
+ * @return
+ */
+ protected DimIndex buildIndex(CompressTable dataCacheVar) {
+ DimIndex dimIndex = new DimIndex(this.indexs);
+ dimIndex.setUnique(isUniqueIndex);
+ dimIndex.buildIndex(dataCacheVar);
+ return dimIndex;
+ }
+
+ /**
+ * 根据索引名和索引值查询匹配的行号
+ *
+ * @param indexName
+ * @param indexValue
+ * @return
+ */
+ public List<Integer> findRowIdByIndex(String indexName, String indexValue) {
+ return nameListIndex == null ? Collections.emptyList() : nameListIndex.getRowIds(indexName, indexValue);
+ }
+
+ /**
+ * 软引用缓存,最大可能保存索引执行器,避免频繁创建,带来额外开销 同时会保护内存不被写爆,当内存不足时自动回收内存
+ */
+ private static ICache<String, IndexExecutor> cache = new SoftReferenceCache<>();
+
+ /**
+ * 先找索引,如果有索引,通过索引匹配。如果没有,全表扫表.
+ *
+ * @param expressionStr 表达式
+ * @param msg 消息
+ * @return 只返回匹配的第一行
+ */
+ public Map<String, Object> matchExpression(String expressionStr, JSONObject msg) {
+ List<Map<String, Object>> rows = matchExpression(expressionStr, msg, true, null);
+ if (rows != null && rows.size() > 0) {
+ return rows.get(0);
+ }
+ return null;
+ }
+
+ /**
+ * 先找索引,如果有索引,通过索引匹配。如果没有,全表扫表
+ *
+ * @param expressionStr 表达式
+ * @param msg 消息
+ * @return 返回全部匹配的行
+ */
+ public List<Map<String, Object>> matchExpression(String expressionStr, JSONObject msg, boolean needAll, String script) {
+ IndexExecutor indexNamelistExecutor = cache.get(expressionStr);
+ if (indexNamelistExecutor == null) {
+ indexNamelistExecutor = new IndexExecutor(expressionStr, getNameSpace(), this.indexs);
+ cache.put(expressionStr, indexNamelistExecutor);
+ }
+ if (indexNamelistExecutor.isSupport()) {
+ return indexNamelistExecutor.match(msg, this, needAll, script);
+ } else {
+ return matchExpressionByLoop(expressionStr, msg, needAll);
+ }
+ }
+
+ /**
+ * 全表扫描,做表达式匹配,返回全部匹配结果
+ *
+ * @param expressionStr
+ * @param msg
+ * @param needAll
+ * @return
+ */
+ protected List<Map<String, Object>> matchExpressionByLoop(String expressionStr, JSONObject msg, boolean needAll) {
+ CompressTable dataCache = this.dataCache;
+ List<Map<String, Object>> rows = matchExpressionByLoop(dataCache.newIterator(), expressionStr, msg, needAll);
+ return rows;
+ }
+
+ /**
+ * 全表扫描,做表达式匹配,返回全部匹配结果。join中有使用
+ *
+ * @param expressionStr
+ * @param msg
+ * @param needAll
+ * @return
+ */
+ public static List<Map<String, Object>> matchExpressionByLoop(Iterator<Map<String, Object>> it, String expressionStr, JSONObject msg, boolean needAll) {
+ List<Map<String, Object>> rows = new ArrayList<>();
+ while (it.hasNext()) {
+ Map<String, Object> values = it.next();
+ Rule ruleTemplete = ExpressionBuilder.createRule("tmp", "tmpRule", expressionStr);
+ Rule rule = ruleTemplete.copy();
+ Map<String, Expression> expressionMap = new HashMap<>();
+ for (Expression expression : rule.getExpressionMap().values()) {
+ expressionMap.put(expression.getConfigureName(), expression);
+ if (RelationExpression.class.isInstance(expression)) {
+ continue;
+ }
+ Object object = expression.getValue();
+ if (object != null && DataTypeUtil.isString(object.getClass())) {
+ String fieldName = (String)object;
+ Object value = values.get(fieldName);
+ if (value != null) {
+ Expression e = expression.copy();
+ e.setValue(value.toString());
+ expressionMap.put(e.getConfigureName(), e);
+ }
+ }
+ }
+ rule.setExpressionMap(expressionMap);
+ boolean matched = rule.execute(msg);
+ if (matched) {
+ rows.add(values);
+ if (needAll == false) {
+ return rows;
+ }
+ }
+ }
+ return rows;
+ }
+
+ protected abstract CompressTable loadData();
+
+ @Override
+ public void destroy() {
+ super.destroy();
+ executorService.shutdown();
+ }
+
+ /**
+ * 设置索引
+ *
+ * @param indexs 字段名称,多个字段";"分隔
+ */
+ public void setIndex(String indexs) {
+ if (StringUtil.isEmpty(indexs)) {
+ return;
+ }
+ List<String> tmp = new ArrayList<>();
+ String[] values = indexs.split(";");
+ this.addIndex(tmp, values);
+ this.indexs = tmp;
+ }
+
+ /**
+ * 建议指定索引,会基于索引建立map,对于等值的判断,可以快速匹配
+ *
+ * @param fieldNames
+ */
+ private String addIndex(List<String> indexs, String... fieldNames) {
+ if (fieldNames == null) {
+ return null;
+ }
+ Arrays.sort(fieldNames);
+ String index = MapKeyUtil.createKey(fieldNames);
+ if (StringUtil.isNotEmpty(index)) {
+ indexs.add(index);
+ }
+ return index;
+ }
+
+ public Long getPollingTimeMintue() {
+ return pollingTimeMintue;
+ }
+
+ public void setPollingTimeMintue(Long pollingTimeMintue) {
+ this.pollingTimeMintue = pollingTimeMintue;
+ }
+
+ public List<String> getIndexs() {
+ return indexs;
+ }
+
+ public void setIndexs(List<String> indexs) {
+ this.indexs = indexs;
+ }
+
+ public CompressTable getDataCache() {
+ return dataCache;
+ }
+
+ public boolean isUniqueIndex() {
+ return isUniqueIndex;
+ }
+
+ public void setUniqueIndex(boolean uniqueIndex) {
+ isUniqueIndex = uniqueIndex;
+ }
+}
diff --git a/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/model/BooleanFieldDBDim.java b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/model/BooleanFieldDBDim.java
new file mode 100644
index 0000000..80dd2d2
--- /dev/null
+++ b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/model/BooleanFieldDBDim.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.dim.model;
+
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.rocketmq.streams.common.metadata.MetaData;
+import org.apache.rocketmq.streams.common.metadata.MetaDataField;
+
+/**
+ * 类似情报表,除了一个核心比对字段和主键,其他都是boolean类型的/或者是只有0/1两个值的字符串和int。可以用这个存储结构
+ */
+public class BooleanFieldDBDim extends DBDim {
+
+ private static final Log LOG = LogFactory.getLog(BooleanFieldDBDim.class);
+
+ /**
+ * 类似情报表,除了一个核心比对字段和主键,其他都是boolean类型的。可以用这个存储结构
+ *
+ * @param metaData
+ * @param notBooleanFieldName
+ * @return
+ */
+ public static boolean support(MetaData metaData, String notBooleanFieldName) {
+ List<MetaDataField> metaDataFields = metaData.getMetaDataFields();
+ for (MetaDataField metaDataField : metaDataFields) {
+ if (metaDataField.getIsPrimary()) {
+ continue;
+ }
+ if (metaDataField.getFieldName().equals(notBooleanFieldName)) {
+ continue;
+ }
+ if (!metaDataField.getDataType().matchClass(Boolean.class)) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/model/DBDim.java b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/model/DBDim.java
new file mode 100644
index 0000000..6180820
--- /dev/null
+++ b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/model/DBDim.java
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.dim.model;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.rocketmq.streams.common.component.ComponentCreator;
+import org.apache.rocketmq.streams.common.configurable.annotation.ENVDependence;
+import org.apache.rocketmq.streams.common.cache.CompressTable;
+import org.apache.rocketmq.streams.common.utils.IPUtil;
+import org.apache.rocketmq.streams.common.utils.MapKeyUtil;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.rocketmq.streams.db.driver.JDBCDriver;
+import org.apache.rocketmq.streams.db.driver.DriverBuilder;
+
+public class DBDim extends AbstractDim {
+
+ private static final Log LOG = LogFactory.getLog(DBDim.class);
+
+ private String jdbcdriver = "com.mysql.jdbc.Driver";
+
+ @ENVDependence
+ private String url;
+
+ @ENVDependence
+ private String userName;
+
+ @ENVDependence
+ private String password;
+
+ private String sql;//sql 会被定时执行
+
+ private static transient AtomicInteger nameCreator = new AtomicInteger(0);
+
+ /**
+ * 是否支持批量查找
+ */
+ protected transient Boolean supportBatch = false;
+
+ public DBDim() {
+ this.setConfigureName(MapKeyUtil.createKey(IPUtil.getLocalIdentification(), System.currentTimeMillis() + "",
+ nameCreator.incrementAndGet() + ""));
+ this.setType(TYPE);
+ }
+
+ @Override
+ protected CompressTable loadData() {
+ List<Map<String, Object>> rows = executeQuery();
+ CompressTable tableCompress = new CompressTable();
+ for (Map<String, Object> row : rows) {
+ tableCompress.addRow(row);
+ }
+ return tableCompress;
+ }
+
+ protected List<Map<String, Object>> executeQuery() {
+ JDBCDriver resource = createResouce();
+ try {
+ List<Map<String, Object>> result = resource.queryForList(sql);
+ ;
+ LOG.info("load configurable's count is " + result.size());
+ return result;
+ } finally {
+ if (resource != null) {
+ resource.destroy();
+ }
+ }
+
+ }
+
+ protected JDBCDriver createResouce() {
+ return DriverBuilder.createDriver(jdbcdriver, url, userName, password);
+ }
+
+ public void setJdbcdriver(String jdbcdriver) {
+ this.jdbcdriver = jdbcdriver;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ public void setUserName(String userName) {
+ this.userName = userName;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+ public void setSql(String sql) {
+ this.sql = sql;
+ }
+
+ public String getJdbcdriver() {
+ return jdbcdriver;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public String getSql() {
+ return sql;
+ }
+
+ public Boolean getSupportBatch() {
+ return supportBatch;
+ }
+
+ public void setSupportBatch(Boolean supportBatch) {
+ this.supportBatch = supportBatch;
+ }
+
+}
diff --git a/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/service/IDimService.java b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/service/IDimService.java
new file mode 100644
index 0000000..c5c4a2e
--- /dev/null
+++ b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/service/IDimService.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.dim.service;
+
+import java.util.List;
+import java.util.Map;
+
+public interface IDimService {
+
+ /**
+ * 做维表join,关系通过表达式表示,返回所有匹配的行。因为msg没有key,表达式中,以下标表示key,如0,1,2。
+ *
+ * @param dimName 维表的名称
+ * @param expressionStr 表达式(0,functionName,filedName)&(1,functionName,filedName)|(2,functionName,filedName)
+ * @param msgs 流数据
+ * @return 符合匹配条件的所有行
+ */
+ Map<String, Object> match(String dimName, String expressionStr, Object... msgs);
+
+ /**
+ * 做维表join,关系通过表达式表示,返回所有匹配的行。
+ *
+ * @param dimName 维表的名称
+ * @param expressionStr 表达式,varName是msg中的key名称(varName,functionName,filedName)&(varName,functionName,filedName)|(varName,functionName,filedName)
+ * @param msgs 流数据
+ * @return 符合匹配条件的所有行
+ */
+ List<Map<String, Object>> matchSupportMultiRow(String dimName, String expressionStr, Map<String, Object> msgs);
+
+ /**
+ * 做维表join,关系通过表达式表示,返回匹配的一行数据,如果有多行匹配,只返回第一行。
+ *
+ * @param dimName 维表的名称
+ * @param expressionStr 表达式,varName是msg中的key名称(varName,functionName,filedName)&(varName,functionName,filedName)|(varName,functionName,filedName)
+ * @param msgs 流数据
+ * @return 返回匹配的一行数据,如果有多行匹配,只返回第一行。
+ */
+ Map<String, Object> match(String dimName, String expressionStr, Map<String, Object> msgs);
+
+ /**
+ * 做维表join,关系通过表达式表示,返回匹配的全部数据。
+ *
+ * @param dimName 维表的名称
+ * @param expressionStr 表达式,varName是msg中的key名称(varName,functionName,filedName)&(varName,functionName,filedName)|(varName,functionName,filedName)
+ * @param msgs 流数据
+ * @param script 对维表字段做处理的函数,在执行表达式前需要先对维表字段做处理,如fiedlName=trim(fieldName)
+ * @return 返回匹配的一行数据,如果有多行匹配,只返回第一行。
+ */
+ List<Map<String, Object>> matchSupportMultiRow(String dimName,
+ String expressionStr, Map<String, Object> msgs, String script);
+}
diff --git a/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/service/impl/DimServiceImpl.java b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/service/impl/DimServiceImpl.java
new file mode 100644
index 0000000..20f22ae
--- /dev/null
+++ b/rocketmq-streams-dim/src/main/java/org/apache/rocketmq/streams/dim/service/impl/DimServiceImpl.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.dim.service.impl;
+
+import java.util.List;
+import java.util.Map;
+
+import com.alibaba.fastjson.JSONObject;
+
+import org.apache.rocketmq.streams.configurable.ConfigurableComponent;
+import org.apache.rocketmq.streams.dim.model.AbstractDim;
+import org.apache.rocketmq.streams.dim.service.IDimService;
+
+public class DimServiceImpl implements IDimService {
+ protected ConfigurableComponent configurableComponent;
+
+ public DimServiceImpl(ConfigurableComponent configurableComponent) {
+ this.configurableComponent = configurableComponent;
+ }
+
+ /**
+ * 传入要比对的字段,进行规则匹配。字段和名单的比对逻辑,写在规则中
+ *
+ * @param msgs 字段名默认为数组的索引,如1,2,3
+ * @return
+ */
+ @Override
+ public Map<String, Object> match(String dimName, String expressionStr, Object... msgs) {
+ if (msgs == null || msgs.length == 0) {
+ return null;
+ }
+ int i = 0;
+ JSONObject jsonObject = new JSONObject();
+ for (Object o : msgs) {
+ jsonObject.put(i + "", o);
+ i++;
+ }
+ return match(dimName, expressionStr, jsonObject);
+ }
+
+ @Override
+ public List<Map<String, Object>> matchSupportMultiRow(String dimName, String expressionStr, Map<String, Object> msgs) {
+ return matchSupportMultiRow(dimName, expressionStr, msgs, null);
+ }
+
+ @Override
+ public List<Map<String, Object>> matchSupportMultiRow(String dimName, String expressionStr, Map<String, Object> msgs, String script) {
+ JSONObject jsonObject = createJsonable(msgs);
+ AbstractDim nameList = configurableComponent.queryConfigurable(AbstractDim.TYPE, dimName);
+ if (nameList != null) {
+ return nameList.matchExpression(expressionStr, jsonObject, true, script);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public Map<String, Object> match(String nameListName, String expressionStr, Map<String, Object> parameters) {
+ JSONObject jsonObject = createJsonable(parameters);
+ AbstractDim nameList = configurableComponent.queryConfigurable(AbstractDim.TYPE, nameListName);
+ if (nameList != null) {
+ return nameList.matchExpression(expressionStr, jsonObject);
+ } else {
+ return null;
+ }
+ }
+
+ private JSONObject createJsonable(Map<String, Object> parameters) {
+ JSONObject jsonObject = null;
+ if (parameters instanceof JSONObject) {
+ jsonObject = (JSONObject)parameters;
+ } else {
+ jsonObject.putAll(parameters);
+ }
+ return jsonObject;
+ }
+
+}
diff --git a/rocketmq-streams-dim/src/test/java/com/aliyun/service/ConfigureLoaderTest.java b/rocketmq-streams-dim/src/test/java/com/aliyun/service/ConfigureLoaderTest.java
new file mode 100644
index 0000000..3f43a2a
--- /dev/null
+++ b/rocketmq-streams-dim/src/test/java/com/aliyun/service/ConfigureLoaderTest.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.aliyun.service;
+
+import org.junit.Test;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+public class ConfigureLoaderTest {
+
+ @Test
+ public void testLoadResource() throws IOException {
+ // BufferedReader br = new BufferedReader(new InputStreamReader(RuleEngineRunner.class.getClassLoader
+ // ().getResourceAsStream(".")));
+ // String line = br.readLine();
+ // while (line != null) {
+ // System.out.println(line);
+ // line = br.readLine();
+ // }
+ }
+}
diff --git a/rocketmq-streams-dim/src/test/java/com/aliyun/service/ExpressionExecutorTest.java b/rocketmq-streams-dim/src/test/java/com/aliyun/service/ExpressionExecutorTest.java
new file mode 100644
index 0000000..380952f
--- /dev/null
+++ b/rocketmq-streams-dim/src/test/java/com/aliyun/service/ExpressionExecutorTest.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.aliyun.service;
+
+import com.alibaba.fastjson.JSONObject;
+import org.apache.rocketmq.streams.filter.builder.ExpressionBuilder;
+import org.apache.rocketmq.streams.filter.operator.expression.SimpleExpression;
+import org.apache.rocketmq.streams.filter.FilterComponent;
+import org.junit.Test;
+
+import java.io.File;
+
+public class ExpressionExecutorTest {
+ private static final String CREDIBLE_PROPERTIES = "credible" + File.separator + "credible.properties";
+ private FilterComponent filterComponent;
+
+ private String namespace = "yundun.credible.net.vistor";
+ private String ruleNameSpace = "credible.rule.net.vistor";
+ private String selectorName = "credible.selector.net.vistor";
+ private String selectorExpression = "(host_uuid,=,0a2153e2-e45c-403f-8d5f-d811f400c3fb)";
+ private String procWriteList = "credible.namelist.proc";
+ private String netWriteList = "credible.namelist.net.vistor";
+
+ private String ruleExpression =
+ "(proc_path,in_resouce," + namespace + "->" + procWriteList + ")&(inner_message,not_in_expression_resouce,'"
+ + namespace + "->" + netWriteList
+ + "->(visitor_ip,=,dest_ip)&(visitor_port,=,dest_port)&(proc_path,=,program_path)')";
+
+ // @Test
+ // public void parseExpression() {
+ // List<Expression> expressions = new ArrayList<>();
+ // List<RelationExpression> relationExpressions = new ArrayList<>();
+ // Expression expression = ExpressionBuilder.createExpression("namespace", ruleExpression,
+ // expressions,
+ // relationExpressions);
+ // }
+
+ public ExpressionExecutorTest() {
+ // FilterComponent filterComponent= new FilterComponent();
+ // filterComponent.init(CREDIBLE_PROPERTIES);
+ // filterComponent.start(null);
+ // this.filterComponent=filterComponent;
+ }
+
+ @Test
+ public void testExecutor() {
+ System.out.println("hello wolrd");
+ JSONObject msg = new JSONObject();
+ msg.put("ip", "1.1.1.1");
+ boolean match = ExpressionBuilder.executeExecute(new SimpleExpression("ip", "=", "1.1.1.1"), msg);
+ System.out.println(match);
+ }
+
+ @Test
+ public void testRelationExecutor() {
+ JSONObject jsonObject = new JSONObject();
+ jsonObject.put("ip", "1.2.2.3");
+ jsonObject.put("uid", "1224");
+ jsonObject.put("vmip", "1.1.1.1");
+
+ boolean value =
+ ExpressionBuilder.executeExecute("namespace", "(ip,=,1.2.2.3)&((uid,=,12214)|(vmip,=,1.1.11.1))",
+ jsonObject);
+ System.out.println(value);
+ }
+}
diff --git a/rocketmq-streams-dim/src/test/java/com/aliyun/service/JsonParserTest.java b/rocketmq-streams-dim/src/test/java/com/aliyun/service/JsonParserTest.java
new file mode 100644
index 0000000..8f58a04
--- /dev/null
+++ b/rocketmq-streams-dim/src/test/java/com/aliyun/service/JsonParserTest.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.aliyun.service;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import org.junit.Test;
+
+public class JsonParserTest {
+
+ @Test
+ public void testJson() {
+ String array =
+ "[{\"value\":\"groupname\",\"key\":\"group_name\"},{\"value\":\"username\",\"key\":\"user_name\"},"
+ + "{\"value\":\"seq\",\"key\":\"index\"},{\"value\":\"egroupid\",\"key\":\"egroup_id\"},"
+ + "{\"value\":\"filepath\",\"key\":\"file_path\"},{\"value\":\"groupid\",\"key\":\"group_id\"},"
+ + "{\"value\":\"pfilename\",\"key\":\"pfile_path\"},{\"value\":\"safe_mode\",\"key\":\"perm\"},"
+ + "{\"value\":\"cmdline\",\"key\":\"cmd_line\"}]";
+ String jsonStr =
+ "{\"className\":\"com.aliyun.filter.result.FieldReNameScript\",\"oldField2NewFiled\":" + array + "}";
+ JSONArray jsonObject = JSON.parseArray(array);
+ JSONObject js = JSON.parseObject(jsonStr);
+ System.out.println(js.toJSONString());
+ }
+}
diff --git a/rocketmq-streams-dim/src/test/java/com/aliyun/service/NameListFunctionTest.java b/rocketmq-streams-dim/src/test/java/com/aliyun/service/NameListFunctionTest.java
new file mode 100644
index 0000000..80d044c
--- /dev/null
+++ b/rocketmq-streams-dim/src/test/java/com/aliyun/service/NameListFunctionTest.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.aliyun.service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.alibaba.fastjson.JSONObject;
+
+import org.apache.rocketmq.streams.dim.model.AbstractDim;
+import org.apache.rocketmq.streams.dim.model.DBDim;
+import org.junit.Test;
+
+public class NameListFunctionTest {
+
+ @Test
+ public void testNameList() {
+ AbstractDim nameList = create();
+ JSONObject msg = new JSONObject();
+ msg.put("ip", "47.105.77.144");
+ msg.put("vpcId", "1");
+ msg.put("now", "2019-07-18 17:33:29");
+ long start = System.currentTimeMillis();
+ }
+
+ @Test
+ public void testNameList2() {
+ AbstractDim nameList = createMapping();
+ JSONObject msg = new JSONObject();
+ msg.put("levelFile", "aegis-vul_record:level");
+ msg.put("levelValue", "high");
+ msg.put("now", "2019-07-18 17:33:29");
+ long start = System.currentTimeMillis();
+ }
+
+ private AbstractDim create() {
+ DBDim dbNameList = new DBDim();
+ dbNameList.setNameSpace("soc");
+ dbNameList.setConfigureName("isoc_field_mappings");
+ dbNameList.setUrl("");
+ dbNameList.setUserName("");
+ dbNameList.setPassword("");
+ dbNameList.setSql("SELECT * FROM `ecs_info` WHERE STATUS=1 LIMIT 1");
+ List<String> ipFieldNames = new ArrayList<>();
+ ipFieldNames.add("public_ips");
+ ipFieldNames.add("inner_ips");
+ ipFieldNames.add("eip");
+ ipFieldNames.add("private_ips");
+ dbNameList.init();
+ return dbNameList;
+ }
+
+ @Test
+ public void testNameListAllRow() {
+ AbstractDim nameList = createMapping();
+ JSONObject msg = new JSONObject();
+ msg.put("levelFile", "aegis-vul_record:level");
+ msg.put("levelValue", "high");
+ msg.put("now", "2019-07-18 17:33:29");
+ long start = System.currentTimeMillis();
+
+ }
+
+ private AbstractDim createMapping() {
+ DBDim dbNameList = new DBDim();
+ dbNameList.setNameSpace("soc");
+ dbNameList.setConfigureName("isoc_field_mappings");
+ dbNameList.setUrl("");
+ dbNameList.setUserName("");
+ dbNameList.setPassword("");
+ dbNameList.setSql("select * from ads_yunsec_ti_url_all_df limit 100000");
+ dbNameList.init();
+ return dbNameList;
+ }
+
+}
diff --git a/rocketmq-streams-dim/src/test/java/com/aliyun/service/TableCompressTest.java b/rocketmq-streams-dim/src/test/java/com/aliyun/service/TableCompressTest.java
new file mode 100644
index 0000000..3c89fd6
--- /dev/null
+++ b/rocketmq-streams-dim/src/test/java/com/aliyun/service/TableCompressTest.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.aliyun.service;
+
+import org.junit.Test;
+
+public class TableCompressTest {
+
+ @Test
+ public void testNameList() throws InterruptedException {
+ }
+}
diff --git a/rocketmq-streams-lease/pom.xml b/rocketmq-streams-lease/pom.xml
new file mode 100755
index 0000000..a0ad067
--- /dev/null
+++ b/rocketmq-streams-lease/pom.xml
@@ -0,0 +1,25 @@
+<?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>
+ <parent>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-streams</artifactId>
+ <version>2.0.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>rocketmq-streams-lease</artifactId>
+ <name>ROCKETMQ STREAMS :: lease</name>
+ <packaging>jar</packaging>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-streams-channel-db</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.rocketmq</groupId>
+ <artifactId>rocketmq-streams-db-operator</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/rocketmq-streams-lease/src/main/java/org/apache/rocketmq/streams/lease/LeaseComponent.java b/rocketmq-streams-lease/src/main/java/org/apache/rocketmq/streams/lease/LeaseComponent.java
new file mode 100644
index 0000000..3a527a3
--- /dev/null
+++ b/rocketmq-streams-lease/src/main/java/org/apache/rocketmq/streams/lease/LeaseComponent.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */package org.apache.rocketmq.streams.lease;
+
+import java.util.Properties;
+
+import org.apache.rocketmq.streams.common.component.AbstractComponent;
+import org.apache.rocketmq.streams.common.component.ComponentCreator;
+import org.apache.rocketmq.streams.common.component.ConfigureDescriptor;
+import org.apache.rocketmq.streams.common.configure.ConfigureFileKey;
+import org.apache.rocketmq.streams.common.utils.StringUtil;
+import org.apache.rocketmq.streams.configurable.service.ConfigurableServcieType;
+import org.apache.rocketmq.streams.lease.service.ILeaseService;
+import org.apache.rocketmq.streams.lease.service.ILeaseStorage;
+import org.apache.rocketmq.streams.lease.service.impl.LeaseServiceImpl;
+import org.apache.rocketmq.streams.lease.service.impl.MockLeaseImpl;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.rocketmq.streams.lease.service.storages.DBLeaseStorage;
+import org.apache.rocketmq.streams.serviceloader.ServiceLoaderComponent;
+
+/**
+ * 通过db实现租约和锁,可以更轻量级,减少其他中间件的依赖 使用主备场景,只有一个实例运行,当当前实例挂掉,在一定时间内,会被其他实例接手 也可以用于全局锁
+ *
+ * @date 1/9/19
+ */
+public class LeaseComponent extends AbstractComponent<ILeaseService> {
+
+ private static LeaseComponent leaseComponent = null;
+ private static final Log LOG = LogFactory.getLog(LeaseComponent.class);
+ private ILeaseService leaseService;
+
+ public LeaseComponent() {
+ initConfigurableServiceDescriptor();
+ addConfigureDescriptor(
+ new ConfigureDescriptor(CONNECT_TYPE, false, ConfigurableServcieType.DEFAULT_SERVICE_NAME));
+ }
+
+ public static LeaseComponent getInstance() {
+ if(leaseComponent==null){
+ synchronized (LeaseComponent.class){
+ if(leaseComponent==null){
+ leaseComponent =ComponentCreator.getComponent(null,LeaseComponent.class);
+ }
+ }
+ }
+ return leaseComponent;
+ }
+
+ @Override
+ public boolean stop() {
+ return true;
+ }
+
+ @Override
+ public ILeaseService getService() {
+ return leaseService;
+ }
+
+ @Override
+ protected boolean startComponent(String namespace) {
+ return true;
+ }
+
+ @Override
+ protected boolean initProperties(Properties properties) {
+ String connectType = properties.getProperty(JDBC_URL);
+ if (StringUtil.isEmpty(connectType)) {
+ MockLeaseImpl mockLease = new MockLeaseImpl();
+ this.leaseService=mockLease;
+ return true;
+ }
+
+ LeaseServiceImpl leaseService= new LeaseServiceImpl();
+ String storageName=ComponentCreator.getProperties().getProperty(ConfigureFileKey.LEASE_STORAGE_NAME);
+ ILeaseStorage storasge=null;
+ if(StringUtil.isEmpty(storageName)){
+ String jdbc = properties.getProperty(AbstractComponent.JDBC_DRIVER);
+ String url = properties.getProperty(AbstractComponent.JDBC_URL);
+ String userName = properties.getProperty(AbstractComponent.JDBC_USERNAME);
+ String password = properties.getProperty(AbstractComponent.JDBC_PASSWORD);
+ storasge=new DBLeaseStorage(jdbc,url,userName,password);
+ }else {
+ storasge= (ILeaseStorage)ServiceLoaderComponent.getInstance(ILeaseStorage.class).loadService(storageName);
+ }
+ leaseService.setLeaseStorage(storasge);
+ this.leaseService=leaseService;
+ return true;
+ }
+}
diff --git a/rocketmq-streams-lease/src/main/java/org/apache/rocketmq/streams/lease/model/LeaseInfo.java b/rocketmq-streams-lease/src/main/java/org/apache/rocketmq/streams/lease/model/LeaseInfo.java
new file mode 100644
index 0000000..469a711
--- /dev/null
+++ b/rocketmq-streams-lease/src/main/java/org/apache/rocketmq/streams/lease/model/LeaseInfo.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.lease.model;
+
+/**
+ * 租约对象,需要创建租约表。
+ */
+
+import java.io.Serializable;
+import java.util.Date;
+
+public class LeaseInfo implements Serializable {
+ private static final long serialVersionUID = 665608838255753618L;
+ private Long id;
+ private Date createTime;
+ private Date updateTime;
+ private String leaseName;//租约名称,多个进程共享一个租约,只要名称相同即可
+ private String leaseUserIp;//区分不同的租约实体,以前默认用ip,但一个机器多个进程的情况下,用ip会区分不开,后续会加上进程号
+ private Date leaseEndDate;//租约到期时间
+ private int status;//租约的有效状态
+ private long version;//版本,通过版本保证更新原子性
+
+ public LeaseInfo() {
+ }
+
+ /**
+ * 建表语句
+ *
+ * @return
+ */
+ public static String createTableSQL() {
+ return "CREATE TABLE IF NOT EXISTS `lease_info` (\n"
+ + " `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',\n"
+ + " `gmt_create` datetime NOT NULL COMMENT '创建时间',\n"
+ + " `gmt_modified` datetime NOT NULL COMMENT '修改时间',\n"
+ + " `lease_name` varchar(255) NOT NULL COMMENT '租约名称',\n"
+ + " `lease_user_ip` varchar(255) NOT NULL COMMENT '租者IP',\n"
+ + " `lease_end_time` varchar(255) NOT NULL COMMENT '租约到期时间',\n"
+ + " `status` int(11) NOT NULL DEFAULT '1' COMMENT '状态',\n"
+ + " `version` bigint(20) NOT NULL COMMENT '版本',\n"
+ + " `candidate_lease_ip` varchar(255) DEFAULT NULL COMMENT '候选租约ip',\n"
+ + " PRIMARY KEY (`id`),\n"
+ + " UNIQUE KEY `uk_name` (`lease_name`)\n"
+ + ") ENGINE=InnoDB AUTO_INCREMENT=8150 DEFAULT CHARSET=utf8 COMMENT='租约信息'\n"
+ + ";";
+ }
+
+ public Long getId() {
+ return this.id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Date getCreateTime() {
+ return this.createTime;
+ }
+
+ public void setCreateTime(Date createTime) {
+ this.createTime = createTime;
+ }
+
+ public Date getUpdateTime() {
+ return this.updateTime;
+ }
+
+ public void setUpdateTime(Date updateTime) {
+ this.updateTime = updateTime;
+ }
+
+ public String getLeaseUserIp() {
+ return this.leaseUserIp;
+ }
+
+ public void setLeaseUserIp(String leaseUserIp) {
+ this.leaseUserIp = leaseUserIp;
+ }
+
+ public Date getLeaseEndDate() {
+ return this.leaseEndDate;
+ }
+
+ public void setLeaseEndDate(Date leaseEndDate) {
+ this.leaseEndDate = leaseEndDate;
+ }
+
+ public int getStatus() {
+ return this.status;
+ }
+
+ public void setStatus(int status) {
+ this.status = status;
+ }
+
+ public String getLeaseName() {
+ return this.leaseName;
+ }
+
+ public void setLeaseName(String leaseName) {
+ this.leaseName = leaseName;
+ }
+
+ public long getVersion() {
+ return this.version;
+ }
+
+ public void setVersion(long version) {
+ this.version = version;
+ }
+
+}
+
diff --git a/rocketmq-streams-lease/src/main/java/org/apache/rocketmq/streams/lease/service/ILeaseGetCallback.java b/rocketmq-streams-lease/src/main/java/org/apache/rocketmq/streams/lease/service/ILeaseGetCallback.java
new file mode 100644
index 0000000..ad63dde
--- /dev/null
+++ b/rocketmq-streams-lease/src/main/java/org/apache/rocketmq/streams/lease/service/ILeaseGetCallback.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.lease.service;
+
+import java.util.Date;
+
+public interface ILeaseGetCallback {
+
+ /**
+ * 当成功获取租约时,回调接口
+ *
+ * @param nextLeaseDate 租约到期时间
+ */
+ void callback(Date nextLeaseDate);
+
+}
diff --git a/rocketmq-streams-lease/src/main/java/org/apache/rocketmq/streams/lease/service/ILeaseService.java b/rocketmq-streams-lease/src/main/java/org/apache/rocketmq/streams/lease/service/ILeaseService.java
new file mode 100644
index 0000000..7782c52
--- /dev/null
+++ b/rocketmq-streams-lease/src/main/java/org/apache/rocketmq/streams/lease/service/ILeaseService.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.lease.service;
+
+import java.util.List;
+import java.util.concurrent.Future;
+
+import org.apache.rocketmq.streams.lease.model.LeaseInfo;
+
+/**
+ * 通过db实现租约和锁,可以更轻量级,减少其他中间件的依赖 使用主备场景,只有一个实例运行,当当前实例挂掉,在一定时间内,会被其他实例接手 也可以用于全局锁
+ */
+public interface ILeaseService {
+
+ /**
+ * 默认锁定时间
+ */
+ static final int DEFALUT_LOCK_TIME = 60 * 5;
+
+ /**
+ * 检查某用户当前时间是否具有租约。这个方法是纯内存操作,无性能开销
+ *
+ * @return true,租约有效;false,租约无效
+ */
+ boolean hasLease(String name);
+
+ /**
+ * 申请租约,会启动一个线程,不停申请租约,直到申请成功。 申请成功后,每 租期/2 续约。 如果目前被其他租户获取租约,只有在对方租约失效,后才允许新的租户获取租约
+ *
+ * @param name 租约名称,无特殊要求,相同名称会竞争租约
+ */
+ void startLeaseTask(String name);
+
+ /**
+ * 申请租约,会启动一个线程,不停申请租约,直到申请成功。 申请成功后,每 租期/2 续约。 如果目前被其他租户获取租约,只有在对方租约失效,后才允许新的租户获取租约
+ *
+ * @param name 租约名称,无特殊要求,相同名称会竞争租约
+ * @param callback 当第一获取租约时,回调此函数
+ */
+ void startLeaseTask(final String name, ILeaseGetCallback callback);
+
+ /**
+ * 申请租约,会启动一个线程,不停申请租约,直到申请成功。 申请成功后,每 租期/2 续约。 如果目前被其他租户获取租约,只有在对方租约失效,后才允许新的租户获取租约
+ *
+ * @param name 租约名称,无特殊要求,相同名称会竞争租约
+ * @param leaseTermSecond 租期,在租期内可以做业务处理,单位是秒
+ * @param callback 当第一获取租约时,回调此函数
+ */
+ void startLeaseTask(final String name, int leaseTermSecond, ILeaseGetCallback callback);
+
+ /**
+ * 申请锁,无论成功与否,立刻返回。如果不释放,最大锁定时间是5分钟
+ *
+ * @param name 业务名称
+ * @param lockerName 锁名称
+ * @return 是否枷锁成功
+ */
+ boolean lock(String name, String lockerName);
+
+ /**
+ * 申请锁,无论成功与否,立刻返回。默认锁定时间是5分钟
+ *
+ * @param name 业务名称
+ * @param lockerName 锁名称
+ * @param lockTimeSecond 如果不释放,锁定的最大时间,单位是秒
+ * @return 是否枷锁成功
+ * @return
+ */
+ boolean lock(String name, String lockerName, int lockTimeSecond);
+
+ /**
+ * 申请锁,如果没有则等待,等待时间可以指定,如果是-1 则无限等待。如果不释放,最大锁定时间是5分钟
+ *
+ * @param name 业务名称
+ * @param lockerName 锁名称
+ * @param waitTime 没获取锁时,最大等待多长时间,如果是-1 则无限等待
+ * @return 是否枷锁成功
+ */
+ boolean tryLocker(String name, String lockerName, long waitTime);
+
+ /**
+ * 申请锁,如果没有则等待,等待时间可以指定,如果是-1 则无限等待。如果不释放,最大锁定时间是lockTimeSecond
+ *
+ * @param name 业务名称
+ * @param lockerName 锁名称
+ * @param waitTime 没获取锁时,最大等待多长时间,如果是-1 则无限等待
+ * @param lockTimeSecond 如果不释放,锁定的最大时间,单位是秒
+ * @return 是否枷锁成功
+ */
+ boolean tryLocker(String name, String lockerName, long waitTime, int lockTimeSecond);
+
+ /**
+ * 释放锁
+ *
+ * @param name
+ * @param lockerName
+ * @return
+ */
+ boolean unlock(String name, String lockerName);
+
+ /**
+ * 对于已经获取锁的,可以通过这个方法,一直持有锁。 和租约的区别是,当释放锁后,无其他实例抢占。无法实现主备模式
+ *
+ * @param name 业务名称
+ * @param lockerName 锁名称
+ * @param lockTimeSecond 租期,这个方法会自动续约,如果不主动释放,会一直持有锁
+ * @return 是否成功获取锁
+ */
+ boolean holdLock(String name, String lockerName, int lockTimeSecond);
+
+ /**
+ * 是否持有锁,不会申请锁。如果以前申请过,且未过期,返回true,否则返回false
+ *
+ * @param name 业务名称
+ * @param lockerName 锁名称
+ * @return
+ */
+ boolean hasHoldLock(String name, String lockerName);
+
+ List<LeaseInfo> queryLockedInstanceByNamePrefix(String name, String lockerNamePrefix);
+
+}
diff --git a/rocketmq-streams-lease/src/main/java/org/apache/rocketmq/streams/lease/service/ILeaseStorage.java b/rocketmq-streams-lease/src/main/java/org/apache/rocketmq/streams/lease/service/ILeaseStorage.java
new file mode 100644
index 0000000..9278bc0
--- /dev/null
+++ b/rocketmq-streams-lease/src/main/java/org/apache/rocketmq/streams/lease/service/ILeaseStorage.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.lease.service;
+
+import java.util.List;
+import java.util.Properties;
+
+import org.apache.rocketmq.streams.lease.model.LeaseInfo;
+
+public interface ILeaseStorage {
+
+ /**
+ * 更新lease info,需要是原子操作,存储保障多线程操作的原子性
+ *
+ * @param leaseInfo 租约表数据
+ * @return
+ */
+ boolean updateLeaseInfo(LeaseInfo leaseInfo);
+
+ /**
+ * 统计这个租约名称下,LeaseInfo对象个数
+ *
+ * @param leaseName 租约名称,无特殊要求,相同名称会竞争租约
+ * @return
+ */
+ Integer countLeaseInfo(String leaseName);
+
+ /**
+ * 查询无效的的租约
+ *
+ * @param leaseName 租约名称,无特殊要求,相同名称会竞争租约
+ * @return
+ */
+ LeaseInfo queryInValidateLease(String leaseName);
+
+ /**
+ * 查询有效的的租约
+ *
+ * @param leaseName 租约名称,无特殊要求,相同名称会竞争租约
+ * @return
+ */
+ LeaseInfo queryValidateLease(String leaseName);
+
+ /**
+ * 按前缀查询有效的租约信息
+ *
+ * @param namePrefix
+ * @return
+ */
+ List<LeaseInfo> queryValidateLeaseByNamePrefix(String namePrefix);
+
+ /**
+ * 增加租约
+ *
+ * @param leaseInfo 租约名称,无特殊要求,相同名称会竞争租约
+ */
+ void addLeaseInfo(LeaseInfo leaseInfo);
+
+}
diff --git a/rocketmq-streams-lease/src/main/java/org/apache/rocketmq/streams/lease/service/ILeaseStorasge.java b/rocketmq-streams-lease/src/main/java/org/apache/rocketmq/streams/lease/service/ILeaseStorasge.java
new file mode 100644
index 0000000..cbfe26e
--- /dev/null
+++ b/rocketmq-streams-lease/src/main/java/org/apache/rocketmq/streams/lease/service/ILeaseStorasge.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.rocketmq.streams.lease.service;
+
+import org.apache.rocketmq.streams.lease.model.LeaseInfo;
+
+public interface ILeaseStorasge {
+
+
+
+ /**
+ * 更新lease info,需要是原子操作,存储保障多线程操作的原子性
+ * @param leaseInfo 租约表数据
+ * @return
+ */
+ boolean updateLeaseInfo(LeaseInfo leaseInfo);
+
+ /**
+ * 统计这个租约名称下,LeaseInfo对象个数
+ * @param leaseName 租约名称,无特殊要求,相同名称会竞争租约
+ * @return
+ */
+ Integer countLeaseInfo(String leaseName);
+
+ /**
+ * 查询无效的的租约
+ * @param leaseName 租约名称,无特殊要求,相同名称会竞争租约
+ * @return
+ */
+ LeaseInfo queryInValidateLease(String leaseName);
+
+
+ /**
+ * 查询无效的的租约
+ * @param leaseName 租约名称,无特殊要求,相同名称会竞争租约
+ * @return
+ */
+ LeaseInfo queryValidateLease(String leaseName);
+
+ /**
+ * 增加租约
+ * @param leaseInfo 租约名称,无特殊要求,相同名称会竞争租约
+ */
+ void addLeaseInfo(LeaseInfo leaseInfo);
+
+
+
+}
diff --git a/rocketmq-streams-lease/src/main/java/org/apache/rocketmq/streams/lease/service/impl/BasedLesaseImpl.java b/rocketmq-streams-lease/src/main/java/org/apache/rocketmq/streams/lease/service/impl/BasedLesaseImpl.java
new file mode 100644
index 0000000..21db98d
--- /dev/null
+++ b/rocketmq-streams-lease/src/main/java/org/apache/rocketmq/streams/lease/service/impl/BasedLesaseImpl.java
@@ -0,0 +1,404 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.lease.service.impl;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.rocketmq.streams.common.utils.DateUtil;
+import org.apache.rocketmq.streams.common.utils.IPUtil;
+import org.apache.rocketmq.streams.common.utils.RuntimeUtil;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.rocketmq.streams.lease.model.LeaseInfo;
+import org.apache.rocketmq.streams.lease.service.ILeaseGetCallback;
+import org.apache.rocketmq.streams.lease.service.ILeaseService;
+import org.apache.rocketmq.streams.lease.service.ILeaseStorage;
+
+public abstract class BasedLesaseImpl implements ILeaseService {
+ private static final Log LOG = LogFactory.getLog(BasedLesaseImpl.class);
+
+ private static final String CONSISTENT_HASH_PREFIX = "consistent_hash_";
+ private static AtomicBoolean syncStart = new AtomicBoolean(false);
+ private static final int synTime = 120; // 5分钟的一致性hash同步时间太久了,改为2分钟
+ protected ScheduledExecutorService taskExecutor = null;
+ protected int leaseTerm = 300 * 2; // 租约时间
+
+ // protected transient JDBCDriver jdbcDataSource = null;
+ protected ILeaseStorage leaseStorage;
+ protected volatile Map<String, Date> leaseName2Date = new ConcurrentHashMap<>(); // 每个lease name对应的租约到期时间
+
+ public BasedLesaseImpl() {
+
+ taskExecutor = new ScheduledThreadPoolExecutor(10);
+
+ }
+
+ /**
+ * lease_name: consistent_hash_ip, lease_user_ip: ip,定时刷新lease_info表,检查一致性hash环的节点情况
+ *
+ * @param name
+ * @return
+ */
+ @Override
+ public boolean hasLease(String name) {
+ // 内存中没有租约信息则表示 没有租约
+ Date leaseEndTime = leaseName2Date.get(name);
+ if (leaseEndTime == null) {
+ // LOG.info("内存中根据 " + name + "没有查询到租约信息,表示没有租约");
+ return false;
+ }
+ // LOG.info("查询是否有租约 name:" + name + " ,当前时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())
+ // + " 租约到期时间 " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(leaseEndTime));
+ // 有租约时间,并且租约时间大于当前时间,表示有租约信息
+ if (new Date().before(leaseEndTime)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private final Map<String, AtomicBoolean> startLeaseMap = new HashMap<>();
+
+ @Override
+ public void startLeaseTask(final String name) {
+ startLeaseTask(name, this.leaseTerm, null);
+ }
+
+ @Override
+ public void startLeaseTask(final String name, ILeaseGetCallback callback) {
+ startLeaseTask(name, this.leaseTerm, callback);
+ }
+
+ @Override
+ public void startLeaseTask(final String name, int leaseTerm, ILeaseGetCallback callback) {
+ ApplyTask applyTask = new ApplyTask(leaseTerm, name, callback);
+ startLeaseTask(name, applyTask, leaseTerm / 2, true);
+ }
+
+ /**
+ * 启动定时器,定时执行任务,确保任务可重入
+ *
+ * @param name
+ * @param runnable 具体任务
+ * @param scheduleTime 调度时间
+ * @param startNow 是否立刻启动一次
+ */
+ protected void startLeaseTask(final String name, Runnable runnable, int scheduleTime, boolean startNow) {
+ AtomicBoolean isStartLease = startLeaseMap.get(name);//多次调用,只启动一次定时任务
+ if (isStartLease == null) {
+ synchronized (this) {
+ isStartLease = startLeaseMap.get(name);
+ if (isStartLease == null) {
+ isStartLease = new AtomicBoolean(false);
+ startLeaseMap.put(name, isStartLease);
+ }
+ }
+ }
+ if (isStartLease.compareAndSet(false, true)) {
+ if (startNow) {
+ runnable.run();
+ }
+ taskExecutor.scheduleWithFixedDelay(runnable, 0, scheduleTime, TimeUnit.SECONDS);
+ }
+ }
+
+ /**
+ * 续约任务
+ */
+ protected class ApplyTask implements Runnable {
+
+ protected String name;
+ protected int leaseTerm;
+ protected ILeaseGetCallback callback;
+
+ public ApplyTask(int leaseTerm, String name) {
+ this(leaseTerm, name, null);
+ }
+
+ public ApplyTask(int leaseTerm, String name, ILeaseGetCallback callback) {
+ this.name = name;
+ this.leaseTerm = leaseTerm;
+ this.callback = callback;
+ }
+
+ @Override
+ public void run() {
+ try {
+ // LOG.info("LeaseServiceImpl name: " + name + "开始获取租约...");
+ AtomicBoolean newApplyLease = new AtomicBoolean(false);
+ Date leaseDate = applyLeaseTask(leaseTerm, name, newApplyLease);
+ if (leaseDate != null) {
+ leaseName2Date.put(name, leaseDate);
+ LOG.info("LeaseServiceImpl, name: " + name + " " + getSelfUser() + " 获取租约成功, 租约到期时间为 "
+ + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(leaseDate));
+ } else {
+ // fix.2020.08.13 这时name对应的租约可能还在有效期内,或者本机还持有租约,需要remove
+ // leaseName2Date.remove(name);
+ LOG.info("LeaseServiceImpl name: " + name + " " + getSelfUser() + " 获取租约失败 ");
+ }
+ if (newApplyLease.get() && callback != null) {
+ callback.callback(leaseDate);
+ }
+ } catch (Exception e) {
+ LOG.error(" LeaseServiceImpl name: " + name + " " + getSelfUser() + " 获取租约出现异常 ", e);
+ }
+
+ }
+ }
+
+ /**
+ * 申请租约,如果当期租约有效,直接更新一个租约周期,如果当前租约无效,先查询是否有有效的租约,如果有申请失败,否则直接申请租约
+ */
+ protected Date applyLeaseTask(int leaseTerm, String name, AtomicBoolean newApplyLease) {
+
+ // 计算下一次租约时间 = 当前时间 + 租约时长
+ Date nextLeaseDate = DateUtil.addSecond(new Date(), leaseTerm);
+
+ // 1 如果已经有租约,则更新租约时间(内存和数据库)即可
+ if (hasLease(name)) {
+ // LOG.info("用户已有租约,更新数据库和内存中的租约信息");
+ // 更新数据库
+ LeaseInfo leaseInfo = queryValidateLease(name);
+ if (leaseInfo == null) {
+ LOG.error("LeaseServiceImpl applyLeaseTask leaseInfo is null");
+ return null;
+ }
+ // fix.2020.08.13,与本机ip相等且满足一致性hash分配策略,才续约,其他情况为null
+ String leaseUserIp = leaseInfo.getLeaseUserIp();
+ if (!leaseUserIp.equals(getSelfUser())) {
+ return null;
+ }
+ leaseInfo.setLeaseEndDate(nextLeaseDate);
+ updateLeaseInfo(leaseInfo);
+ return nextLeaseDate;
+ }
+
+ // 2 没有租约情况 判断是否可以获取租约,只要租约没有被其他人获取,则说明有有效租约
+ boolean success = canGetLease(name);
+ if (!success) { // 表示被其他机器获取到了有效的租约
+ // LOG.info("其他机器获取到了有效的租约");
+ return null;
+ }
+
+ // 3 没有租约而且可以获取租约的情况,则尝试使用数据库原子更新的方式获取租约,保证只有一台机器成功获取租约,而且可以运行
+ boolean flag = tryGetLease(name, nextLeaseDate);
+ if (flag) { // 获取租约成功
+ newApplyLease.set(true);
+ return nextLeaseDate;
+ }
+ return null;
+
+ }
+
+ /**
+ * 查询数据库,自己是否在租期内或没有被其他人租用
+ *
+ * @return
+ */
+ protected boolean canGetLease(String name) {
+ LeaseInfo leaseInfo = queryValidateLease(name);
+ if (leaseInfo == null) {
+ return true;
+ }
+ // fix.2020.08.13,租约ip为本机ip,且与一致性hash分配ip一致,才是有效租约
+ String leaseUserIp = leaseInfo.getLeaseUserIp();
+ if (leaseUserIp.equals(getSelfUser())) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 更新数据库,占用租期并更新租期时间
+ *
+ * @param time
+ */
+ protected boolean tryGetLease(String name, Date time) {
+ // LOG.info("尝试获取租约 lease name is : " + name + " 下次到期时间: "
+ // + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(time));
+ LeaseInfo validateLeaseInfo = queryValidateLease(name);
+
+ if (validateLeaseInfo == null) {// 这里有两种情况 1 数据库里面没有租约信息 2 数据库里面有租约信息但是已经过期
+ Integer count = countLeaseInfo(name);
+ if (count == null || count == 0) {// 表示现在数据库里面没有任何租约信息,插入租约成功则表示获取成功,失败表示在这一时刻其他机器获取了租约
+ // LOG.info("数据库中暂时没有租约信息,尝试原子插入租约:" + name);
+ // fix.2020.08.13,经过一致性hash计算,该名字的任务不应该在本机执行,直接返回,无需插入。只有分配到hash执行权限的机器才可以插入并获取租约
+ if (!getSelfUser().equals(getConsistentHashHost(name))) {
+ return false;
+ }
+ validateLeaseInfo = new LeaseInfo();
+ validateLeaseInfo.setLeaseName(name);
+ validateLeaseInfo.setLeaseUserIp(getSelfUser());
+ validateLeaseInfo.setLeaseEndDate(time);
+ validateLeaseInfo.setStatus(1);
+ validateLeaseInfo.setVersion(1);
+ if (insert(validateLeaseInfo)) {
+ LOG.info("数据库中暂时没有租约信息,原子插入成功,获取租约成功:" + name);
+ return true;
+ } else {
+ LOG.info("数据库中暂时没有租约信息,原子插入失败,已经被其他机器获取租约:" + name);
+ return false;
+ }
+ } else { // 表示数据库里面有一条但是无效,这里需要两台机器按照version进行原子更新,更新成功的获取租约
+ // LOG.info("数据库中有一条无效的租约信息,尝试根据版本号去原子更新租约信息:" + name);
+ LeaseInfo inValidateLeaseInfo = queryInValidateLease(name);
+ if (inValidateLeaseInfo == null) {// 说明这个时候另外一台机器获取成功了
+ LOG.info("另外一台机器获取成功了租约:" + name);
+ return false;
+ }
+ // fix.2020.08.13,机器重启之后,该名字的任务已经不分配在此机器上执行,直接返回,无需更新数据库
+ if (!getSelfUser().equals(getConsistentHashHost(name))) {
+ return false;
+ }
+ inValidateLeaseInfo.setLeaseName(name);
+ inValidateLeaseInfo.setLeaseUserIp(getSelfUser());
+ inValidateLeaseInfo.setLeaseEndDate(time);
+ inValidateLeaseInfo.setStatus(1);
+ boolean success = updateDBLeaseInfo(inValidateLeaseInfo);
+ if (success) {
+ LOG.info("LeaseServiceImpl 原子更新租约成功,当前机器获取到了租约信息:" + name);
+ } else {
+ LOG.info("LeaseServiceImpl 原子更新租约失败,租约被其他机器获取:" + name);
+ }
+ return success;
+ }
+
+ } else { // 判断是否是自己获取了租约,如果是自己获取了租约则更新时间(内存和数据库),
+ // 这里是为了解决机器重启的情况,机器重启,内存中没有租约信息,但是实际上该用户是有租约权限的
+ // fix.2020.08.13,租约的ip与本机ip相等,且满足一致性hash策略,才会被本机执行
+ String leaseUserIp = validateLeaseInfo.getLeaseUserIp();
+ if (leaseUserIp.equals(getSelfUser())) {
+ // 如果当期用户有租约信息,则更新数据库
+ validateLeaseInfo.setLeaseEndDate(time);
+ boolean hasUpdate = updateLeaseInfo(validateLeaseInfo);
+ if (hasUpdate) {
+ LOG.info(
+ "LeaseServiceImpl机器重启情况,当前用户有租约信息,并且更新数据库成功,租约信息为 name :" + validateLeaseInfo.getLeaseName()
+ + " ip : " + validateLeaseInfo.getLeaseUserIp() + " 到期时间 : " + new SimpleDateFormat(
+ "yyyy-MM-dd HH:mm:ss").format(validateLeaseInfo.getLeaseEndDate()));
+ return true;
+ } else {
+ LOG.info("LeaseServiceImpl 机器重启情况,当前用户有租约信息,并且更新数据库失败,表示失去租约:" + name);
+ return false;
+ }
+ }
+ // LOG.info("LeaseServiceImpl 租约被其他机器获取,租约信息为 name :" + validateLeaseInfo.getLeaseName() + " ip : "
+ // + validateLeaseInfo.getLeaseUserIp() + " 到期时间 : "
+ // + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(validateLeaseInfo.getLeaseEndDate()));
+ return false;
+ }
+
+ }
+
+ protected LeaseInfo queryValidateLease(String name) {
+ //String sql = "SELECT * FROM lease_info WHERE lease_name ='" + name + "' and status=1 and lease_end_time>now()";
+ //// LOG.info("LeaseServiceImpl query validate lease sql:" + sql);
+ //return queryLease(name, sql);
+ return leaseStorage.queryValidateLease(name);
+ }
+
+ protected List<LeaseInfo> queryValidateLeaseByNamePrefix(String namePrefix) {
+ return leaseStorage.queryValidateLeaseByNamePrefix(namePrefix);
+ }
+
+ /**
+ * 如果发生唯一索引冲突返回失败
+ *
+ * @param leaseInfo
+ * @return
+ */
+ private boolean insert(LeaseInfo leaseInfo) {
+ try {
+ addLeaseInfo(leaseInfo);
+ return true;
+ } catch (Exception e) {
+ LOG.error("LeaseServiceImpl insert error", e);
+ return false;
+ }
+ }
+
+ /**
+ * 更新时需要加version=当前version,如果更新数据条数为0,返回false
+ *
+ * @param leaseInfo
+ * @return
+ */
+ protected boolean updateDBLeaseInfo(LeaseInfo leaseInfo) {
+ return updateLeaseInfo(leaseInfo);
+ }
+
+ protected boolean updateLeaseInfo(LeaseInfo leaseInfo) {
+
+ return leaseStorage.updateLeaseInfo(leaseInfo);
+ }
+
+ protected Integer countLeaseInfo(String name) {
+
+ return leaseStorage.countLeaseInfo(name);
+ }
+
+ protected LeaseInfo queryInValidateLease(String name) {
+
+ return leaseStorage.queryInValidateLease(name);
+ }
+
+ protected void addLeaseInfo(LeaseInfo leaseInfo) {
+
+ leaseStorage.addLeaseInfo(leaseInfo);
+
+ }
+
+ /**
+ * 本地ip地址作为自己的唯一标识
+ *
+ * @return
+ */
+ public static String getLocalName() {
+ return IPUtil.getLocalIdentification() + ":" + Optional.ofNullable(RuntimeUtil.getPid()).orElse("UNKNOWN");
+ }
+
+ /**
+ * 本地ip地址作为自己的唯一标识
+ *
+ * @return
+ */
+ public String getSelfUser() {
+ return getLocalName();
+ }
+
+ private String getConsistentHashHost(String name) {
+ //if (StringUtil.isEmpty(leaseConsistentHashSuffix)) {
+ // return getSelfUser();
+ //}
+ //return consistentHashInstance.getCandidateNode(name);
+ return getSelfUser();
+ }
+
+ public void setLeaseStorage(ILeaseStorage leaseStorage) {
+ this.leaseStorage = leaseStorage;
+ }
+}
diff --git a/rocketmq-streams-lease/src/main/java/org/apache/rocketmq/streams/lease/service/impl/LeaseServiceImpl.java b/rocketmq-streams-lease/src/main/java/org/apache/rocketmq/streams/lease/service/impl/LeaseServiceImpl.java
new file mode 100644
index 0000000..860710a
--- /dev/null
+++ b/rocketmq-streams-lease/src/main/java/org/apache/rocketmq/streams/lease/service/impl/LeaseServiceImpl.java
@@ -0,0 +1,275 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.lease.service.impl;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.rocketmq.streams.common.utils.DateUtil;
+import org.apache.rocketmq.streams.common.utils.MapKeyUtil;
+import org.apache.rocketmq.streams.lease.model.LeaseInfo;
+import org.apache.rocketmq.streams.lease.service.ILeaseService;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class LeaseServiceImpl extends BasedLesaseImpl {
+
+ private static final Log LOG = LogFactory.getLog(LeaseServiceImpl.class);
+
+ private transient ConcurrentHashMap<String, HoldLockTask> holdLockTasks = new ConcurrentHashMap();
+
+ protected ConcurrentHashMap<String, HoldLockFunture> seizeLockingFuntures = new ConcurrentHashMap<>();
+ //如果是抢占锁状态中,则不允许申请锁
+
+ public LeaseServiceImpl() {
+ super();
+ }
+
+ /**
+ * 尝试获取锁,可以等待waitTime,如果到点未返回,则直接返回。如果是-1,则一直等待
+ *
+ * @param name 业务名称
+ * @param lockerName 锁名称
+ * @param waitTime 等待时间,是微秒单位
+ * @return
+ */
+ @Override
+ public boolean tryLocker(String name, String lockerName, long waitTime) {
+ return tryLocker(name, lockerName, waitTime, ILeaseService.DEFALUT_LOCK_TIME);
+ }
+
+ @Override
+ public boolean tryLocker(String name, String lockerName, long waitTime, int lockTimeSecond) {
+ long now = System.currentTimeMillis();
+ boolean success = lock(name, lockerName, lockTimeSecond);
+ while (!success) {
+ if (waitTime > -1 && (System.currentTimeMillis() - now > waitTime)) {
+ break;
+ }
+ success = lock(name, lockerName, lockTimeSecond);
+ if (success) {
+ return success;
+ }
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ LOG.error("LeaseServiceImpl try locker error", e);
+ }
+ }
+ return success;
+
+ }
+
+ @Override
+ public boolean lock(String name, String lockerName) {
+ return lock(name, lockerName, ILeaseService.DEFALUT_LOCK_TIME);
+ }
+
+ @Override
+ public boolean lock(String name, String lockerName, int leaseSecond) {
+ lockerName = createLockName(name, lockerName);
+ Future future = seizeLockingFuntures.get(lockerName);
+ if (future != null && ((HoldLockFunture)future).isDone == false) {
+ return false;
+ }
+ Date nextLeaseDate =
+ DateUtil.addSecond(new Date(), leaseSecond);// 默认锁定5分钟,用完需要立刻释放.如果时间不同步,可能导致锁失败
+ boolean success = tryGetLease(lockerName, nextLeaseDate);// 申请锁,锁的时间是leaseTerm
+ return success;
+ }
+
+ @Override
+ public boolean unlock(String name, String lockerName) {
+ // LOG.info("LeaseServiceImpl unlock,name:" + name);
+ lockerName = createLockName(name, lockerName);
+ LeaseInfo validateLeaseInfo = queryValidateLease(lockerName);
+ if (validateLeaseInfo == null) {
+ LOG.warn("LeaseServiceImpl unlock,validateLeaseInfo is null,lockerName:" + lockerName);
+ }
+ if (validateLeaseInfo != null && validateLeaseInfo.getLeaseUserIp().equals(getSelfUser())) {
+ validateLeaseInfo.setStatus(0);
+ updateDBLeaseInfo(validateLeaseInfo);
+ }
+ HoldLockTask holdLockTask = holdLockTasks.remove(lockerName);
+ if (holdLockTask != null) {
+ holdLockTask.close();
+ }
+ leaseName2Date.remove(lockerName);
+ return false;
+ }
+
+ /**
+ * 如果有锁,则一直持有,如果不能获取,则结束。和租约不同,租约是没有也会尝试重试,一备对方挂机,自己可以接手工作
+ *
+ * @param name
+ * @param secondeName
+ * @param lockTimeSecond 获取锁的时间
+ * @return
+ */
+ @Override
+ public boolean holdLock(String name, String secondeName, int lockTimeSecond) {
+ if (hasHoldLock(name, secondeName)) {
+ return true;
+ }
+ synchronized (this) {
+ if (hasHoldLock(name, secondeName)) {
+ return true;
+ }
+ String lockerName = createLockName(name, secondeName);
+ Date nextLeaseDate =
+ DateUtil.addSecond(new Date(), lockTimeSecond);
+ boolean success = tryGetLease(lockerName, nextLeaseDate);// 申请锁,锁的时间是leaseTerm
+ if (success == false) {
+ return false;
+ }
+ leaseName2Date.put(lockerName, nextLeaseDate);
+
+ if (!holdLockTasks.containsKey(lockerName)) {
+ HoldLockTask holdLockTask = new HoldLockTask(lockTimeSecond, lockerName, this);
+ holdLockTask.start();
+ holdLockTasks.putIfAbsent(lockerName, holdLockTask);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * 是否持有锁,不访问数据库,直接看本地
+ *
+ * @param name
+ * @param secondeName
+ * @return
+ */
+ @Override
+ public boolean hasHoldLock(String name, String secondeName) {
+ String lockerName = createLockName(name, secondeName);
+ return hasLease(lockerName);
+ }
+
+ @Override
+ public List<LeaseInfo> queryLockedInstanceByNamePrefix(String name, String lockerNamePrefix) {
+ String leaseNamePrefix = MapKeyUtil.createKey(name, lockerNamePrefix);
+ return queryValidateLeaseByNamePrefix(leaseNamePrefix);
+ }
+
+ private String createLockName(String name, String lockerName) {
+ return MapKeyUtil.createKey(name, lockerName);
+ }
+
+ private class HoldLockTask extends ApplyTask {
+ protected volatile boolean iscontinue = true;
+ protected LeaseServiceImpl leaseService;
+ protected ScheduledExecutorService scheduledExecutor;
+
+ public HoldLockTask(int leaseTerm, String name, LeaseServiceImpl leaseService) {
+ super(leaseTerm, name);
+ this.leaseService = leaseService;
+ scheduledExecutor = new ScheduledThreadPoolExecutor(1);
+
+ }
+
+ public void start() {
+ scheduledExecutor.scheduleWithFixedDelay(this, leaseTerm / 2, leaseTerm / 2, TimeUnit.SECONDS);
+ }
+
+ public void close() {
+ iscontinue = false;
+ if (scheduledExecutor != null) {
+ scheduledExecutor.shutdown();
+ }
+ }
+
+ public boolean isIscontinue() {
+ return iscontinue;
+ }
+
+ @Override
+ public void run() {
+ try {
+ if (!iscontinue) {
+ return;
+ }
+ Date leaseDate = applyLeaseTask(leaseTerm, name, new AtomicBoolean(false));
+ if (leaseDate != null) {
+ leaseName2Date.put(name, leaseDate);
+ LOG.debug("LeaseServiceImpl, name: " + name + " " + getSelfUser() + " 续约锁成功, 租约到期时间为 "
+ + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(leaseDate));
+ } else {
+ iscontinue = false;
+ synchronized (leaseService) {
+ holdLockTasks.remove(name);
+ }
+ LOG.info("LeaseServiceImpl name: " + name + " " + getSelfUser() + " 续约锁失败,续锁程序会停止");
+ }
+ } catch (Exception e) {
+ iscontinue = false;
+ LOG.error(" LeaseServiceImpl name: " + name + " " + getSelfUser() + " 续约锁出现异常,续锁程序会停止", e);
+ }
+
+ }
+
+ }
+
+ /**
+ * 抢占锁的future,必须等锁超时才能继续获取锁
+ */
+ protected class HoldLockFunture implements Future<Boolean> {
+ private volatile boolean isDone = false;
+ private volatile Date date = null;
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ throw new RuntimeException("can not cancel");
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return false;
+ }
+
+ @Override
+ public boolean isDone() {
+ if (date != null && System.currentTimeMillis() - date.getTime() >= 0) {
+ isDone = true;
+ return isDone;
+ }
+ return false;
+ }
+
+ @Override
+ public Boolean get() throws InterruptedException, ExecutionException {
+ while (isDone() == false) {
+ Thread.sleep(1000);
+ }
+ return true;
+ }
+
+ private long startTime = System.currentTimeMillis();
+
+ @Override
+ public Boolean get(long timeout, TimeUnit unit)
+ throws InterruptedException, ExecutionException, TimeoutException {
+
+ throw new RuntimeException("can not support timeout ");
+ }
+
+ }
+}
diff --git a/rocketmq-streams-lease/src/main/java/org/apache/rocketmq/streams/lease/service/impl/MockLeaseImpl.java b/rocketmq-streams-lease/src/main/java/org/apache/rocketmq/streams/lease/service/impl/MockLeaseImpl.java
new file mode 100644
index 0000000..d952581
--- /dev/null
+++ b/rocketmq-streams-lease/src/main/java/org/apache/rocketmq/streams/lease/service/impl/MockLeaseImpl.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.lease.service.impl;
+
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.apache.rocketmq.streams.common.utils.DateUtil;
+import org.apache.rocketmq.streams.lease.model.LeaseInfo;
+import org.apache.rocketmq.streams.lease.service.ILeaseGetCallback;
+import org.apache.rocketmq.streams.lease.service.ILeaseService;
+
+/**
+ * 在内存和文件模式下使用,所有的申请都会返回true,主要用来做业务测试
+ */
+public class MockLeaseImpl implements ILeaseService {
+ @Override
+ public boolean hasLease(String name) {
+ return true;
+ }
+
+ @Override
+ public void startLeaseTask(String name) {
+
+ }
+
+ @Override
+ public void startLeaseTask(String name, ILeaseGetCallback callback) {
+ callback.callback(DateUtil.addMinute(new Date(), 1));
+ }
+
+ @Override
+ public void startLeaseTask(String name, int leaseTerm, ILeaseGetCallback callback) {
+
+ }
+
+ @Override
+ public boolean lock(String name, String lockerName) {
+ return true;
+ }
+
+ @Override
+ public boolean lock(String name, String lockerName, int lockTimeSecond) {
+ return true;
+ }
+
+ @Override
+ public boolean tryLocker(String name, String lockerName, long waitTime) {
+ return true;
+ }
+
+ @Override
+ public boolean tryLocker(String name, String lockerName, long waitTime, int lockTimeSecond) {
+ return true;
+ }
+
+ @Override
+ public boolean unlock(String name, String lockerName) {
+ return true;
+ }
+
+ @Override
+ public boolean holdLock(String name, String lockerName, int lockTimeSecond) {
+ return true;
+ }
+
+ @Override
+ public boolean hasHoldLock(String name, String lockerName) {
+ return true;
+ }
+
+ @Override
+ public List<LeaseInfo> queryLockedInstanceByNamePrefix(String name, String lockerNamePrefix) {
+ return null;
+ }
+
+}
diff --git a/rocketmq-streams-lease/src/main/java/org/apache/rocketmq/streams/lease/service/storages/DBLeaseStorage.java b/rocketmq-streams-lease/src/main/java/org/apache/rocketmq/streams/lease/service/storages/DBLeaseStorage.java
new file mode 100644
index 0000000..b99132d
--- /dev/null
+++ b/rocketmq-streams-lease/src/main/java/org/apache/rocketmq/streams/lease/service/storages/DBLeaseStorage.java
@@ -0,0 +1,229 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.lease.service.storages;
+
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.rocketmq.streams.common.utils.DateUtil;
+import org.apache.rocketmq.streams.common.utils.SQLUtil;
+import org.apache.rocketmq.streams.common.utils.StringUtil;
+import org.apache.rocketmq.streams.db.driver.JDBCDriver;
+import org.apache.rocketmq.streams.db.driver.DriverBuilder;
+import org.apache.rocketmq.streams.lease.model.LeaseInfo;
+import org.apache.rocketmq.streams.lease.service.ILeaseStorage;
+
+public class DBLeaseStorage implements ILeaseStorage {
+ private static final Log LOG = LogFactory.getLog(DBLeaseStorage.class);
+ protected JDBCDriver jdbcDataSource;
+ private String url;
+ protected String userName;
+ protected String password;
+ protected String jdbc;
+
+ public DBLeaseStorage(String jdbc, String url, String userName, String password) {
+ this.jdbc = jdbc;
+ this.url = url;
+ this.userName = userName;
+ this.password = password;
+ jdbcDataSource = DriverBuilder.createDriver(jdbc, url, userName, password);
+ }
+
+ @Override
+ public boolean updateLeaseInfo(LeaseInfo leaseInfo) {
+ String sql = "UPDATE lease_info SET version=version+1,status=#{status},gmt_modified=now()";
+ String whereSQL = " WHERE id=#{id} and version=#{version}";
+
+ if (StringUtil.isNotEmpty(leaseInfo.getLeaseName())) {
+ sql += ",lease_name=#{leaseName}";
+ }
+ if (StringUtil.isNotEmpty(leaseInfo.getLeaseUserIp())) {
+ sql += ",lease_user_ip=#{leaseUserIp}";
+ }
+ if (leaseInfo.getLeaseEndDate() != null) {
+ sql += ",lease_end_time=#{leaseEndDate}";
+ }
+ sql += whereSQL;
+ sql = SQLUtil.parseIbatisSQL(leaseInfo, sql);
+ try {
+ int count = getOrCreateJDBCDataSource().update(sql);
+ boolean success = count > 0;
+ if (success) {
+ synchronized (this) {
+ leaseInfo.setVersion(leaseInfo.getVersion() + 1);
+ }
+ } else {
+ System.out.println(count);
+ }
+ return success;
+ } catch (Exception e) {
+ LOG.error("LeaseServiceImpl updateLeaseInfo excuteUpdate error", e);
+ throw new RuntimeException("execute sql error " + sql, e);
+ }
+ }
+
+ @Override
+ public Integer countLeaseInfo(String leaseName) {
+ String sql = "SELECT count(*) as c FROM lease_info WHERE lease_name = '" + leaseName + "' and status = 1";
+ try {
+
+ List<Map<String, Object>> rows = getOrCreateJDBCDataSource().queryForList(sql);
+ if (rows == null || rows.size() == 0) {
+ return null;
+ }
+ Long value = (Long)rows.get(0).get("c");
+ return value.intValue();
+ } catch (Exception e) {
+ throw new RuntimeException("execute sql error " + sql, e);
+ }
+ }
+
+ @Override
+ public LeaseInfo queryInValidateLease(String leaseName) {
+ String sql = "SELECT * FROM lease_info WHERE lease_name ='" + leaseName + "' and status=1 and lease_end_time<'" + DateUtil.getCurrentTimeString() + "'";
+ LOG.info("LeaseServiceImpl queryInValidateLease builder:" + sql);
+ return queryLease(leaseName, sql);
+ }
+
+ @Override
+ public LeaseInfo queryValidateLease(String leaseName) {
+ String sql = "SELECT * FROM lease_info WHERE lease_name ='" + leaseName + "' and status=1 and lease_end_time>now()";
+ return queryLease(leaseName, sql);
+ }
+
+ @Override
+ public List<LeaseInfo> queryValidateLeaseByNamePrefix(String namePrefix) {
+ String sql = "SELECT * FROM lease_info WHERE lease_name like '" + namePrefix + "%' and status=1 and lease_end_time>now()";
+ try {
+ List<LeaseInfo> leaseInfos = new ArrayList<>();
+ List<Map<String, Object>> rows = getOrCreateJDBCDataSource().queryForList(sql);
+ if (rows == null || rows.size() == 0) {
+ return null;
+ }
+ for (Map<String, Object> row : rows) {
+ LeaseInfo leaseInfo = convert(row);
+ leaseInfos.add(leaseInfo);
+ }
+
+ return leaseInfos;
+ } catch (Exception e) {
+ throw new RuntimeException("execute sql error " + sql, e);
+ }
+ }
+
+ @Override
+ public void addLeaseInfo(LeaseInfo leaseInfo) {
+ String sql =
+ " REPLACE INTO lease_info(lease_name,lease_user_ip,lease_end_time,status,version,gmt_create,gmt_modified)"
+ + " VALUES (#{leaseName},#{leaseUserIp},#{leaseEndDate},#{status},#{version},now(),now())";
+ sql = SQLUtil.parseIbatisSQL(leaseInfo, sql);
+ try {
+
+ getOrCreateJDBCDataSource().execute(sql);
+ } catch (Exception e) {
+ LOG.error("LeaseServiceImpl execute sql error,sql:" + sql, e);
+ throw new RuntimeException("execute sql error " + sql, e);
+ }
+ }
+
+ protected JDBCDriver getOrCreateJDBCDataSource() {
+ if (this.jdbcDataSource == null || !this.jdbcDataSource.isValidate()) {
+ synchronized (this) {
+ if (this.jdbcDataSource == null || !this.jdbcDataSource.isValidate()) {
+ this.jdbcDataSource =
+ DriverBuilder.createDriver(this.jdbc, this.url, this.userName, this.password);
+ }
+ }
+ }
+ return jdbcDataSource;
+ }
+
+ protected LeaseInfo queryLease(String name, String sql) {
+ try {
+ List<Map<String, Object>> rows = getOrCreateJDBCDataSource().queryForList(sql);
+ if (rows == null || rows.size() == 0) {
+ return null;
+ }
+ return convert(rows.get(0));
+ } catch (Exception e) {
+ throw new RuntimeException("execute sql error " + sql, e);
+ }
+ }
+
+ protected LeaseInfo convert(Map<String, Object> map) {
+ LeaseInfo leaseInfo = new LeaseInfo();
+ leaseInfo.setId(getMapLongValue("id", map));
+ leaseInfo.setCreateTime(getMapDateValue("gmt_create", map));
+ leaseInfo.setLeaseEndDate(getMapDateValue("lease_end_time", map));
+ leaseInfo.setLeaseName(getMapValue("lease_name", map, String.class));
+ leaseInfo.setLeaseUserIp(getMapValue("lease_user_ip", map, String.class));
+ Integer stauts = getMapValue("status", map, Integer.class);
+ if (stauts != null) {
+ leaseInfo.setStatus(stauts);
+ }
+ leaseInfo.setUpdateTime(getMapDateValue("gmt_modified", map));
+ Long version = getMapLongValue("version", map);
+ if (version != null) {
+ leaseInfo.setVersion(version);
+ }
+ return leaseInfo;
+ }
+
+ @SuppressWarnings("unchecked")
+ private <T> T getMapValue(String fieldName, Map<String, Object> map, Class<T> integerClass) {
+ Object value = map.get(fieldName);
+ if (value == null) {
+ return null;
+ }
+ return (T)value;
+ }
+
+ private Long getMapLongValue(String fieldName, Map<String, Object> map) {
+ Object value = map.get(fieldName);
+ if (value == null) {
+ return null;
+ }
+ if (value instanceof Long) {
+ return (Long)value;
+ }
+ if (value instanceof BigInteger) {
+ return ((BigInteger)value).longValue();
+ }
+ return null;
+ }
+
+ private Date getMapDateValue(String fieldName, Map<String, Object> map) {
+ Object value = map.get(fieldName);
+ if (value == null) {
+ return null;
+ }
+ if (value instanceof Date) {
+ return (Date)value;
+ }
+ if (value instanceof String) {
+ return DateUtil.parseTime(((String)value));
+ }
+ return null;
+
+ }
+
+}
diff --git a/rocketmq-streams-lease/src/test/java/org/apache/rocketmq/streams/lease/LeaseComponentTest.java b/rocketmq-streams-lease/src/test/java/org/apache/rocketmq/streams/lease/LeaseComponentTest.java
new file mode 100644
index 0000000..6404e9f
--- /dev/null
+++ b/rocketmq-streams-lease/src/test/java/org/apache/rocketmq/streams/lease/LeaseComponentTest.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.rocketmq.streams.lease;
+
+import java.util.Date;
+
+import org.apache.rocketmq.streams.common.component.ComponentCreator;
+import org.apache.rocketmq.streams.common.configure.ConfigureFileKey;
+import org.apache.rocketmq.streams.db.driver.DriverBuilder;
+import org.apache.rocketmq.streams.db.driver.JDBCDriver;
+import org.apache.rocketmq.streams.lease.model.LeaseInfo;
+import org.apache.rocketmq.streams.lease.service.ILeaseGetCallback;
+import org.apache.rocketmq.streams.lease.service.ILeaseService;
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class LeaseComponentTest {
+
+ private String URL = "";
+ protected String USER_NAME = "";
+ protected String PASSWORD = "";
+
+ public LeaseComponentTest() {
+
+ //正式使用时,在配置文件配置
+ ComponentCreator.getProperties().put(ConfigureFileKey.CONNECT_TYPE, "DB");
+ ComponentCreator.getProperties().put(ConfigureFileKey.JDBC_URL, URL);//数据库连接url
+ ComponentCreator.getProperties().put(ConfigureFileKey.JDBC_USERNAME, USER_NAME);//用户名
+ ComponentCreator.getProperties().put(ConfigureFileKey.JDBC_PASSWORD, PASSWORD);//password
+
+ JDBCDriver driver = DriverBuilder.createDriver();
+ driver.execute(LeaseInfo.createTableSQL());
+ }
+
+ @Test
+ public void testLease() throws InterruptedException {
+ String leaseName = "lease.test";
+ int leaseTime = 5;
+ LeaseComponent.getInstance().getService().startLeaseTask(leaseName, leaseTime, new ILeaseGetCallback() {
+ @Override
+ public void callback(Date nextLeaseDate) {
+ System.out.println("I get lease");
+ }
+ });
+ assertTrue(LeaseComponent.getInstance().getService().hasLease(leaseName));
+ Thread.sleep(5000);
+ assertTrue(LeaseComponent.getInstance().getService().hasLease(leaseName));//会一直续约
+ Thread.sleep(5000);
+ assertTrue(LeaseComponent.getInstance().getService().hasLease(leaseName));//会一直续约
+ }
+
+ @Test
+ public void testLock() throws InterruptedException {
+ String name = "dipper";
+ String lockName = "lease.test";
+ int leaseTime = 5;
+ boolean success = LeaseComponent.getInstance().getService().lock(name, lockName, leaseTime);//锁定5秒钟
+ assertTrue(success);//获取锁
+ Thread.sleep(6000);
+ assertFalse(LeaseComponent.getInstance().getService().hasHoldLock(name, lockName));//超期释放
+ }
+
+ /**
+ * holdlock是一直持有锁,和租约的区别是,当释放锁后,无其他实例抢占
+ *
+ * @throws InterruptedException
+ */
+ @Test
+ public void testHoldLock() throws InterruptedException {
+ String name = "dipper";
+ String lockName = "lease.test";
+ int leaseTime = 6;
+ boolean success = LeaseComponent.getInstance().getService().holdLock(name, lockName, leaseTime);//锁定5秒钟
+ assertTrue(success);//获取锁
+ Thread.sleep(8000);
+ assertTrue(LeaseComponent.getInstance().getService().hasHoldLock(name, lockName));//会自动续约,不会释放,可以手动释放
+ LeaseComponent.getInstance().getService().unlock(name, lockName);
+ assertFalse(LeaseComponent.getInstance().getService().hasHoldLock(name, lockName));
+ }
+
+ @Test
+ public void testHoldLockContinue() throws InterruptedException {
+ String name = "dipper";
+ String lockName = "lease.test";
+ int leaseTime = 6;
+ boolean success = holdLock(name, lockName, leaseTime);//锁定5秒钟
+ while (true) {
+ Thread.sleep(1000);
+ System.out.println(holdLock(name, lockName, leaseTime));
+ }
+ }
+
+ protected boolean holdLock(String name, String lockName, int leaseTime) {
+ ILeaseService leaseService = LeaseComponent.getInstance().getService();
+ if (leaseService.hasHoldLock(name, lockName)) {
+ return true;
+ }
+
+ boolean success = leaseService.holdLock(name, lockName, leaseTime);
+ return success;
+ }
+
+}
diff --git a/rocketmq-streams-lease/src/test/resources/log4j.xml b/rocketmq-streams-lease/src/test/resources/log4j.xml
new file mode 100755
index 0000000..7812fe7
--- /dev/null
+++ b/rocketmq-streams-lease/src/test/resources/log4j.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE log4j:configuration SYSTEM "http://toolkit.alibaba-inc.com/dtd/log4j/log4j.dtd">
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+
+ <appender name="Console" class="org.apache.log4j.ConsoleAppender">
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%d{ISO8601} %l [%t] %-5p - %m%n%n"/>
+ </layout>
+ <filter class="org.apache.log4j.varia.LevelRangeFilter">
+ <param name="LevelMin" value="INFO"/>
+ <param name="LevelMax" value="ERROR"/>
+ </filter>
+ </appender>
+
+ <root>
+ <priority value="INFO"/>
+ <appender-ref ref="Console"/>
+ </root>
+
+</log4j:configuration>
\ No newline at end of file