Compare commits

..
Author SHA1 Message Date
lcybo 67a3a1d955 Merge pull request #827 from lcybo/metrics_support
Merge master updates
2018-08-09 22:50:32 +08:00
Chuanyi Li a857b87388 Merge branch 'master' into metrics_support 2018-08-09 22:44:39 +08:00
七锋 b57ac0bcdf fixed merge and blob encoding example 2018-08-09 16:56:13 +08:00
charles.lin ea6391d1c3 Merge branch 'branch-rds-support' 2018-08-09 16:44:12 +08:00
lcybo a2eb128698 Merge pull request #826 from lcybo/metrics_support
Metrics support
2018-08-08 22:56:44 +08:00
Chuanyi Li 760f2dda80 Enhance heartbeat, rowscount. 2018-08-08 22:53:08 +08:00
Chuanyi Li bf9dc0073d Merge branch 'master' into metrics_support 2018-08-08 21:01:22 +08:00
Chuanyi Li d4797a8fb9 Merge master branch. 2018-08-08 20:35:14 +08:00
Chuanyi Li 060bd8e31d Reformat code. 2018-08-08 20:10:34 +08:00
Chuanyi Li 88de747ed5 Improve sink blocking time resolution. 2018-08-08 07:53:44 +08:00
lcybo 725d36aa98 Merge pull request #819 from lcybo/metrics_support
canal 性能监控改动版,merge到开发分支(metrics_support)
2018-08-07 23:02:37 +08:00
Chuanyi Li d24e10fb3e Canal metrics snapshot version. 2018-08-07 22:52:30 +08:00
Chuanyi Li fdae646e26 Merge branch 'master' into metrics_support 2018-08-07 19:46:30 +08:00
Chuanyi Li L 40c3e65341 Refactor metrics code. 2018-08-07 17:17:33 +08:00
Chuanyi Li b5a1898cec Prometheus refactor. 2018-08-07 08:18:01 +08:00
Chuanyi Li L 680faea2bb Add listener to write. 2018-08-06 17:22:28 +08:00
winger 1cd77bf551 fix bug: kafka get row data for performance 2018-08-06 15:29:14 +08:00
qmzhang 9786faba9a instance由manager管理时,CanalParameter#MetaMode增加本地文件方式,本地meta.dat存储位点信息; 2018-08-06 15:29:14 +08:00
七锋 325fe89aed fixed issue #801 , optimizer docker 2018-08-06 15:29:13 +08:00
七锋 ffeec8fc86 fixed issue #801 , support canal docker 2018-08-06 15:29:13 +08:00
winger 7662c9241e 支持GTID模式下,通过获取当前gtid值 2018-08-06 15:29:13 +08:00
Chuanyi Li L d01195ac82 Merge branch 'master' into metrics_support 2018-08-06 15:26:44 +08:00
Chuanyi Li 8b186d1330 canal server metrics refactor. 2018-08-06 08:12:59 +08:00
Chuanyi Li L b7431eb7d0 Reformat code. 2018-08-03 17:42:57 +08:00
Chuanyi Li L c7fc149e7e Netty server/client profiler skeleton. 2018-08-03 17:32:48 +08:00
Chuanyi Li L 368554166c Builder of Aggregator. 2018-08-02 17:36:22 +08:00
Chuanyi Li L c4ee350717 Merge branch 'master' of https://github.com/alibaba/canal 2018-08-02 14:55:01 +08:00
Chuanyi Li ae0f43d1c7 netty3 future listener for metrics. 2018-08-01 08:17:55 +08:00
482 changed files with 16741 additions and 44364 deletions
-6
View File
@@ -1,6 +0,0 @@
coverage:
status:
# pull-requests only
patch:
default:
threshold: 0.1%
@@ -1,8 +0,0 @@
---
name: Any Questions on Canal
about: Ask whatever you want to know or confusion about Canal
---
## Question
<!-- You can ask any question about this project -->
@@ -1,29 +0,0 @@
---
name: Bug Report for Canal
about: Create a report to help us improve
---
- [ ] I have searched the [issues](https://github.com/alibaba/canal/issues) of this repository and believe that this is not a duplicate.
- [ ] I have checked the [FAQ](https://github.com/alibaba/canal/wiki/FAQ) of this repository and believe that this is not a duplicate.
### environment
* canal version
* mysql version
### Issue Description
### Steps to reproduce
### Expected behaviour
### Actual behaviour
If there is an exception, please attach the exception trace:
```
Just put your stack trace here!
```
@@ -1,16 +0,0 @@
---
name: Feature Request of Canal
about: Suggest an idea for this project
---
## Why you need it?
<!-- Is your feature request related to a problem? Please describe in details -->
## How it could be?
<!--A clear and concise description of what you want to happen. For a computer fan, you can explain more about input of the feature, and output of it.-->
## Other related information
<!-- Add any other context or screenshots about the feature request here.-->
-2
View File
@@ -16,5 +16,3 @@ jtester.properties
.DS_Store
*.tar.gz
*.rpm
client-adapter/example/
*.dat
BIN
View File
Binary file not shown.
-1
View File
@@ -1 +0,0 @@
distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip
-23
View File
@@ -1,23 +0,0 @@
language: java
sudo: false # faster builds
jdk:
- openjdk11
- oraclejdk11
- openjdk8
- oraclejdk8
cache:
directories:
- $HOME/.m2
install: true
script:
- travis_wait 30 ./mvnw clean install -DskipTests=false -Dcheckstyle.skip=true -Dmaven.javadoc.skip=true
after_success:
- bash <(curl -s https://codecov.io/bash)
after_failure:
- if [ -f canal.log ]; then echo "------TAIL of canal.log------"; tail -n 1000 canal.log; echo "------END of canal.log------"; fi
+16 -50
View File
@@ -1,12 +1,13 @@
<div class="blog_content">
<div class="iteye-blog-content-contain">
[![Build Status](https://travis-ci.com/alibaba/canal.svg?branch=master)](https://travis-ci.com/alibaba/canal)
[![codecov](https://codecov.io/gh/alibaba/canal/branch/master/graph/badge.svg)](https://codecov.io/gh/alibaba/canal)
![maven](https://img.shields.io/maven-central/v/com.alibaba.otter/canal.svg)
![license](https://img.shields.io/github/license/alibaba/canal.svg)
[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/alibaba/canal.svg)](http://isitmaintained.com/project/alibaba/canal "Average time to resolve an issue")
[![Percentage of issues still open](http://isitmaintained.com/badge/open/alibaba/canal.svg)](http://isitmaintained.com/project/alibaba/canal "Percentage of issues still open")
<h3>最新更新</h3>
<ol>
<li>canal QQ讨论群已经建立,群号:161559791 ,欢迎加入进行技术讨论。</li>
<li>canal消费端项目开源: Otter(分布式数据库同步系统),地址:<a href="https://github.com/alibaba/otter">https://github.com/alibaba/otter</a></li>
<li>Canal已在阿里云推出商业化版本 <a href="https://www.aliyun.com/product/dts?spm=a2c4g.11186623.cloudEssentials.80.srdwr7">数据传输服务DTS</a>, 开通即用,免去部署维护的昂贵使用成本。DTS针对阿里云RDS、DRDS等产品进行了适配,解决了Binlog日志回收,主备切换、VPC网络切换等场景下的订阅高可用问题。同时,针对RDS进行了针对性的性能优化。出于稳定性、性能及成本的考虑,强烈推荐阿里云用户使用DTS产品。<a href="https://help.aliyun.com/document_detail/26592.html?spm=a2c4g.11174283.6.539.t1Y91E">DTS产品使用文档</a></li>
DTS支持阿里云RDS&DRDS的Binlog日志实时订阅,现推出首月免费体验,限时限量,<a href="https://common-buy.aliyun.com/?commodityCode=dtspre&request=%7b%22dts_function%22%3a%22data_subscribe%22%7d">立即体验>>></a>
</ol>
<h1>背景</h1>
<p style="font-size: 14px;"> 早期,阿里巴巴B2B公司因为存在杭州和美国双机房部署,存在跨机房同步的业务需求。不过早期的数据库同步业务,主要是基于trigger的方式获取增量变更,不过从2010年开始,阿里系公司开始逐步的尝试基于数据库的日志解析,获取增量变更进行同步,由此衍生出了增量订阅&amp;消费的业务,从此开启了一段新纪元。</p>
@@ -45,16 +46,6 @@
<li>canal解析binary log对象(原始为byte流)</li>
</ol>
<h1>重要版本更新说明</h1>
canal 1.1.x系列,参考release文档:<a href="https://github.com/alibaba/canal/releases">版本发布信息</a>
1. 整体性能测试&优化,提升了150%. #726 参考: 【[Performance](https://github.com/alibaba/canal/wiki/Performance)】
2. 原生支持prometheus监控 #765 【[Prometheus QuickStart](https://github.com/alibaba/canal/wiki/Prometheus-QuickStart)】
3. 原生支持kafka消息投递 #695 【[Canal Kafka/RocketMQ QuickStart](https://github.com/alibaba/canal/wiki/Canal-Kafka-RocketMQ-QuickStart)】
4. 原生支持aliyun rds的binlog订阅 (解决自动主备切换/oss binlog离线解析) 参考: 【[Aliyun RDS QuickStart](https://github.com/alibaba/canal/wiki/aliyun-RDS-QuickStart)】
5. 原生支持docker镜像 #801 参考: 【[Docker QuickStart](https://github.com/alibaba/canal/wiki/Docker-QuickStart)】
<h1>相关文档</h1>
See the wiki page for : <a href="https://github.com/alibaba/canal/wiki" >wiki文档</a>
@@ -63,41 +54,20 @@ See the wiki page for : <a href="https://github.com/alibaba/canal/wiki" >wiki文
<ul>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/Home">Home</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/Introduction">Introduction</a></li>
<li>
<a class="internal present" href="https://github.com/alibaba/canal/wiki/QuickStart">QuickStart</a>
<ul>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/Docker-QuickStart">Docker QuickStart</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/Canal-Kafka-RocketMQ-QuickStart">Canal Kafka/RocketMQ QuickStart</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/aliyun-RDS-QuickStart">Aliyun RDS QuickStart</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/Prometheus-QuickStart">Prometheus QuickStart</a></li>
</ul>
</li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/AdminGuide">AdminGuide</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/QuickStart">QuickStart</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/ClientExample">ClientExample</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/AdminGuide">AdminGuide</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/ClientAPI">ClientAPI</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/Performance">Performance</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/DevGuide">DevGuide</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/BinlogChange%28mysql5.6%29">BinlogChange(Mysql5.6)</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/BinlogChange%28MariaDB%29">BinlogChange(MariaDB)</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/TableMetaTSDB">TableMetaTSDB</a></li>
<li><a href="http://alibaba.github.com/canal/release.html">ReleaseNotes</a></li>
<li><a href="https://github.com/alibaba/canal/releases">Download</a></li>
<li><a class="internal present" href="https://github.com/alibaba/canal/wiki/FAQ">FAQ</a></li>
</ul>
<h1>多语言业务</h1>
1. canal整体交互协议设计上使用了protobuf3.0,理论上可以支持绝大部分的多语言场景,欢迎大家提交多客户端的PR
* canal java客户端: <a href="https://github.com/alibaba/canal/wiki/ClientExample"> https://github.com/alibaba/canal/wiki/ClientExample </a>
* canal c#客户端开源项目地址<a href="https://github.com/dotnetcore/CanalSharp"> https://github.com/dotnetcore/CanalSharp </a>
* canal go客户端开源项目地址:<a href="https://github.com/CanalClient/canal-go"> https://github.com/CanalClient/canal-go </a>
2. canal作为MySQL binlog的增量获取工具,可以将数据投递到MQ系统中,比如Kafka/RocketMQ,可以借助于MQ的多语言能力
* 参考文档: [Canal Kafka/RocketMQ QuickStart](https://github.com/alibaba/canal/wiki/Canal-Kafka-RocketMQ-QuickStart)
<h1>相关资料</h1>
* ADC阿里技术嘉年华分享ppt (放在google docs上,可能需要翻墙): <a href="https://docs.google.com/presentation/d/1MkszUPYRDkfVPz9IqOT1LLT5d9tuwde_WC8GZvjaDRg/edit?usp=sharing">ppt下载</href>
* [与阿里巴巴的RocketMQ配合使用](https://github.com/apache/RocketMQ)
* [与阿里巴巴的RocketMQ配合使用](https://github.com/alibaba/RocketMQ)
<h1>相关开源</h1>
<ol>
@@ -121,13 +91,9 @@ See the wiki page for : <a href="https://github.com/alibaba/canal/wiki" >wiki文
<li>报告issue<a href="https://github.com/alibaba/canal/issues">issues</a></li>
</ol>
<h3>最新更新</h3>
<ol>
<li>canal发布重大版本更新1.1.0,具体releaseNode参考:<a href="https://github.com/alibaba/canal/releases/tag/canal-1.1.0">https://github.com/alibaba/canal/releases/tag/canal-1.1.0</a></li>
<li>canal c#客户端开源项目地址<a href="https://github.com/dotnetcore/CanalSharp"> https://github.com/dotnetcore/CanalSharp </a>,推荐! </li>
<li>canal QQ讨论群已经建立,群号:161559791 ,欢迎加入进行技术讨论。</li>
<li>canal消费端项目开源: Otter(分布式数据库同步系统),地址:<a href="https://github.com/alibaba/otter">https://github.com/alibaba/otter</a></li>
<li>Canal已在阿里云推出商业化版本 <a href="https://www.aliyun.com/product/dts?spm=a2c4g.11186623.cloudEssentials.80.srdwr7">数据传输服务DTS</a>, 开通即用,免去部署维护的昂贵使用成本。DTS针对阿里云RDS、DRDS等产品进行了适配,解决了Binlog日志回收,主备切换、VPC网络切换等场景下的订阅高可用问题。同时,针对RDS进行了针对性的性能优化。出于稳定性、性能及成本的考虑,强烈推荐阿里云用户使用DTS产品。<a href="https://help.aliyun.com/document_detail/26592.html?spm=a2c4g.11174283.6.539.t1Y91E">DTS产品使用文档</a></li>
DTS支持阿里云RDS&DRDS的Binlog日志实时订阅,现推出首月免费体验,限时限量,<a href="https://common-buy.aliyun.com/?commodityCode=dtspre&request=%7b%22dts_function%22%3a%22data_subscribe%22%7d">立即体验>>></a>
</ol>
<pre>
【招聘】阿里巴巴中间件团队招聘JAVA高级工程师
岗位主要为技术型内容(非业务部门),阿里中间件整个体系对于未来想在技术上有所沉淀的同学还是非常有帮助的
工作地点:杭州、北京均可. ps. 阿里待遇向来都是不错的,有意者可以QQ、微博私聊.
具体招聘内容:https://job.alibaba.com/zhaopin/position_detail.htm?positionId=32666
</pre>
-545
View File
@@ -1,545 +0,0 @@
## 基本说明
canal 1.1.1版本之后, 增加客户端数据落地的适配及启动功能, 目前支持功能:
* 客户端启动器
* 同步管理REST接口
* 日志适配器, 作为DEMO
* HBase的数据同步(表对表同步), ETL功能
* (后续支持) ElasticSearch多表数据同步,ETL功能
## 环境版本
* 操作系统:无要求
* java版本: jdk1.8 以上
* canal 版本: 请下载最新的安装包,本文以当前v1.1.1 的canal.deployer-1.1.1.tar.gz为例
* MySQL版本 5.7.18
* HBase版本: Apache HBase 1.1.2, 若和服务端版本不一致可自行替换客户端HBase依赖
## 一、适配器启动器
client-adapter分为适配器和启动器两部分, 适配器为多个fat jar, 每个适配器会将自己所需的依赖打成一个包, 以SPI的方式让启动器动态加载
启动器为 SpringBoot 项目, 支持canal-client启动的同时提供相关REST管理接口, 运行目录结构为:
```
- bin
restart.sh
startup.bat
startup.sh
stop.sh
- lib
client-adapter.logger-1.1.1-jar-with-dependencies.jar
client-adapter.hbase-1.1.1-jar-with-dependencies.jar
...
- conf
application.yml
- hbase
mytest_person2.yml
- logs
```
以上目录结构最终会打包成 canal-adapter-launcher.tar.gz 压缩包
## 二、启动器
### 2.1 启动器配置 application.yml
#### canal相关配置部分说明
```
canal.conf:
canalServerHost: 127.0.0.1:11111 # 对应单机模式下的canal server的ip:port
zookeeperHosts: slave1:2181 # 对应集群模式下的zk地址, 如果配置了canalServerHost, 则以canalServerHost为准
bootstrapServers: slave1:6667 # kafka或rocketMQ地址, 与canalServerHost不能并存
flatMessage: true # 扁平message开关, 是否以json字符串形式投递数据, 仅在kafka/rocketMQ模式下有效
canalInstances: # canal实例组, 如果是tcp模式可配置此项
- instance: example # 对应canal destination
groups: # 对应适配器分组, 分组间的适配器并行运行
- outAdapters: # 适配器列表, 分组内的适配串行运行
- name: logger # 适配器SPI名
- name: hbase
properties: # HBase相关连接参数
hbase.zookeeper.quorum: slave1
hbase.zookeeper.property.clientPort: 2181
zookeeper.znode.parent: /hbase
mqTopics: # MQ topic组, 如果是kafka或者rockeMQ模式可配置此项, 与canalInstances不能并存
- mqMode: kafka # MQ的模式: kafak/rocketMQ
topic: example # MQ topic
groups: # group组
- groupId: g2 # group id
outAdapters: # 适配器列表, 以下配置和canalInstances中一样
- name: logger
```
#### 适配器相关配置部分说明
```
adapter.conf:
datasourceConfigs: # 数据源配置列表, 数据源将在适配器中用于ETL、数据同步回查等使用
defaultDS: # 数据源 dataSourceKey, 适配器中通过该值获取指定数据源
url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true
username: root
password: 121212
adapterConfigs: # 适配器内部配置列表
- hbase/mytest_person2.yml # 类型/配置文件名, 这里示例就是对应HBase适配器hbase目录下的mytest_person2.yml文件
```
## 2.2 同步管理REST接口
#### 2.2.1 查询所有订阅同步的canal destination或MQ topic
```
curl http://127.0.0.1:8081/destinations
```
#### 2.2.2 数据同步开关
```
curl http://127.0.0.1:8081/syncSwitch/example/off -X PUT
```
针对 example 这个canal destination/MQ topic 进行开关操作. off代表关闭, 该destination/topic下的同步将阻塞或者断开连接不再接收数据, on代表开启
注: 如果在配置文件中配置了 zookeeperHosts 项, 则会使用分布式锁来控制HA中的数据同步开关, 如果是单机模式则使用本地锁来控制开关
#### 2.2.3 数据同步开关状态
```
curl http://127.0.0.1:8081/syncSwitch/example
```
查看指定 canal destination/MQ topic 的数据同步开关状态
#### 2.2.4 手动ETL
```
curl http://127.0.0.1:8081/etl/hbase/mytest_person2.yml -X POST -d "params=2018-10-21 00:00:00"
```
导入数据到指定类型的库
#### 2.2.5 查看相关库总数据
```
curl http://127.0.0.1:8081/count/hbase/mytest_person2.yml
```
### 2.3 启动canal-adapter示例
#### 2.3.1 启动canal server (单机模式), 参考: [Canal QuickStart](https://github.com/alibaba/canal/wiki/QuickStart)
#### 2.3.2 修改conf/application.yml为:
```
server:
port: 8081
logging:
level:
com.alibaba.otter.canal.client.adapter.hbase: DEBUG
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
default-property-inclusion: non_null
canal.conf:
canalServerHost: 127.0.0.1:11111
flatMessage: true
canalInstances:
- instance: example
adapterGroups:
- outAdapters:
- name: logger
```
启动
```
bin/startup.sh
```
## 三、HBase适配器
### 3.1 修改启动器配置: application.yml
```
server:
port: 8081
logging:
level:
com.alibaba.otter.canal.client.adapter.hbase: DEBUG
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
default-property-inclusion: non_null
canal.conf:
canalServerHost: 127.0.0.1:11111
flatMessage: true
srcDataSources:
defaultDS:
url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true
username: root
password: 121212
canalInstances:
- instance: example
adapterGroups:
- outAdapters:
- name: hbase
properties:
hbase.zookeeper.quorum: slave1
hbase.zookeeper.property.clientPort: 2181
zookeeper.znode.parent: /hbase
```
adapter将会自动加载 conf/hbase 下的所有.yml结尾的配置文件
### 3.2 适配器表映射文件
修改 conf/hbase/mytest_person.yml文件:
```
dataSourceKey: defaultDS # 对应application.yml中的datasourceConfigs下的配置
hbaseMapping: # mysql--HBase的单表映射配置
mode: STRING # HBase中的存储类型, 默认统一存为String, 可选: #PHOENIX #NATIVE #STRING
# NATIVE: 以java类型为主, PHOENIX: 将类型转换为Phoenix对应的类型
destination: example # 对应 canal destination/MQ topic 名称
database: mytest # 数据库名/schema名
table: person # 表名
hbaseTable: MYTEST.PERSON # HBase表名
family: CF # 默认统一Column Family名称
uppercaseQualifier: true # 字段名转大写, 默认为true
commitBatch: 3000 # 批量提交的大小, ETL中用到
#rowKey: id,type # 复合字段rowKey不能和columns中的rowKey并存
# 复合rowKey会以 '|' 分隔
columns: # 字段映射, 如果不配置将自动映射所有字段,
# 并取第一个字段为rowKey, HBase字段名以mysql字段名为主
id: ROWKE
name: CF:NAME
email: EMAIL # 如果column family为默认CF, 则可以省略
type: # 如果HBase字段和mysql字段名一致, 则可以省略
c_time:
birthday:
```
如果涉及到类型转换,可以如下形式:
```
...
columns:
id: ROWKE$STRING
...
type: TYPE$BYTE
...
```
类型转换涉及到Java类型和Phoenix类型两种, 分别定义如下:
```
#Java 类型转换, 对应配置 mode: NATIVE
$DEFAULT
$STRING
$INTEGER
$LONG
$SHORT
$BOOLEAN
$FLOAT
$DOUBLE
$BIGDECIMAL
$DATE
$BYTE
$BYTES
```
```
#Phoenix 类型转换, 对应配置 mode: PHOENIX
$DEFAULT 对应PHOENIX里的VARCHAR
$UNSIGNED_INT 对应PHOENIX里的UNSIGNED_INT 4字节
$UNSIGNED_LONG 对应PHOENIX里的UNSIGNED_LONG 8字节
$UNSIGNED_TINYINT 对应PHOENIX里的UNSIGNED_TINYINT 1字节
$UNSIGNED_SMALLINT 对应PHOENIX里的UNSIGNED_SMALLINT 2字节
$UNSIGNED_FLOAT 对应PHOENIX里的UNSIGNED_FLOAT 4字节
$UNSIGNED_DOUBLE 对应PHOENIX里的UNSIGNED_DOUBLE 8字节
$INTEGER 对应PHOENIX里的INTEGER 4字节
$BIGINT 对应PHOENIX里的BIGINT 8字节
$TINYINT 对应PHOENIX里的TINYINT 1字节
$SMALLINT 对应PHOENIX里的SMALLINT 2字节
$FLOAT 对应PHOENIX里的FLOAT 4字节
$DOUBLE 对应PHOENIX里的DOUBLE 8字节
$BOOLEAN 对应PHOENIX里的BOOLEAN 1字节
$TIME 对应PHOENIX里的TIME 8字节
$DATE 对应PHOENIX里的DATE 8字节
$TIMESTAMP 对应PHOENIX里的TIMESTAMP 12字节
$UNSIGNED_TIME 对应PHOENIX里的UNSIGNED_TIME 8字节
$UNSIGNED_DATE 对应PHOENIX里的UNSIGNED_DATE 8字节
$UNSIGNED_TIMESTAMP 对应PHOENIX里的UNSIGNED_TIMESTAMP 12字节
$VARCHAR 对应PHOENIX里的VARCHAR 动态长度
$VARBINARY 对应PHOENIX里的VARBINARY 动态长度
$DECIMAL 对应PHOENIX里的DECIMAL 动态长度
```
如果不配置将以java对象原生类型默认映射转换
### 3.3 启动HBase数据同步
#### 创建HBase表
在HBase shell中运行:
```
create 'MYTEST.PERSON', {NAME=>'CF'}
```
#### 启动canal-adapter启动器
```
bin/startup.sh
```
#### 验证
修改mysql mytest.person表的数据, 将会自动同步到HBase的MYTEST.PERSON表下面, 并会打出DML的log
## 四、关系型数据库适配器
RDB adapter 用于适配mysql到任意关系型数据库(需支持jdbc)的数据同步及导入
### 4.1 修改启动器配置: application.yml, 这里以oracle目标库为例
```
server:
port: 8081
logging:
level:
com.alibaba.otter.canal.client.adapter.rdb: DEBUG
......
canal.conf:
canalServerHost: 127.0.0.1:11111
srcDataSources:
defaultDS:
url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true
username: root
password: 121212
canalInstances:
- instance: example
groups:
- outAdapters:
- name: rdb # 指定为rdb类型同步
key: oracle1 # 指定adapter的唯一key, 与表映射配置中outerAdapterKey对应
properties:
jdbc.driverClassName: oracle.jdbc.OracleDriver # jdbc驱动名, jdbc的jar包需要自行放致lib目录下
jdbc.url: jdbc:oracle:thin:@localhost:49161:XE # jdbc url
jdbc.username: mytest # jdbc username
jdbc.password: m121212 # jdbc password
threads: 5 # 并行执行的线程数, 默认为1
commitSize: 3000 # 批次提交的最大行数
```
其中 outAdapter 的配置: name统一为rdb, key为对应的数据源的唯一标识需和下面的表映射文件中的outerAdapterKey对应, properties为目标库jdb的相关参数
adapter将会自动加载 conf/rdb 下的所有.yml结尾的表映射配置文件
### 4.2 适配器表映射文件
修改 conf/rdb/mytest_user.yml文件:
```
dataSourceKey: defaultDS # 源数据源的key, 对应上面配置的srcDataSources中的值
destination: example # cannal的instance或者MQ的topic
outerAdapterKey: oracle1 # adapter key, 对应上面配置outAdapters中的key
concurrent: true # 是否按主键hase并行同步, 并行同步的表必须保证主键不会更改及主键不能为其他同步表的外键!!
dbMapping:
database: mytest # 源数据源的database/shcema
table: user # 源数据源表名
targetTable: mytest.tb_user # 目标数据源的库名.表名
targetPk: # 主键映射
id: id # 如果是复合主键可以换行映射多个
# mapAll: true # 是否整表映射, 要求源表和目标表字段名一模一样 (如果targetColumns也配置了映射,则以targetColumns配置为准)
targetColumns: # 字段映射, 格式: 目标表字段: 源表字段, 如果字段名一样源表字段名可不填
id:
name:
role_id:
c_time:
test1:
```
导入的类型以目标表的元类型为准, 将自动转换
### 4.3 启动RDB数据同步
#### 将目标库的jdbc jar包放入lib文件夹, 这里放入ojdbc6.jar
#### 启动canal-adapter启动器
```
bin/startup.sh
```
#### 验证
修改mysql mytest.user表的数据, 将会自动同步到Oracle的MYTEST.TB_USER表下面, 并会打出DML的log
## 五、ElasticSearch适配器
### 5.1 修改启动器配置: application.yml
```
server:
port: 8081
logging:
level:
com.alibaba.otter.canal.client.adapter.es: DEBUG
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
default-property-inclusion: non_null
canal.conf:
canalServerHost: 127.0.0.1:11111
flatMessage: true
srcDataSources:
defaultDS:
url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true
username: root
password: 121212
canalInstances:
- instance: example
adapterGroups:
- outAdapters:
- name: es
hosts: 127.0.0.1:9300 # es 集群地址, 逗号分隔
properties:
cluster.name: elasticsearch # es cluster name
```
adapter将会自动加载 conf/es 下的所有.yml结尾的配置文件
### 5.2 适配器表映射文件
修改 conf/es/mytest_user.yml文件:
```
dataSourceKey: defaultDS # 源数据源的key, 对应上面配置的srcDataSources中的值
destination: example # cannal的instance或者MQ的topic
esMapping:
_index: mytest_user # es 的索引名称
_type: _doc # es 的doc名称
_id: _id # es 的_id, 如果不配置该项必须配置下面的pk项_id则会由es自动分配
# pk: id # 如果不需要_id, 则需要指定一个属性为主键属性
# sql映射
sql: "select a.id as _id, a.name as _name, a.role_id as _role_id, b.role_name as _role_name,
a.c_time as _c_time, c.labels as _labels from user a
left join role b on b.id=a.role_id
left join (select user_id, group_concat(label order by id desc separator ';') as labels from label
group by user_id) c on c.user_id=a.id"
# objFields:
# _labels: array:; # 数组或者对象属性, array:; 代表以;字段里面是以;分隔的
# _obj: obj:{"test":"123"}
etlCondition: "where a.c_time>='{0}'" # etl 的条件参数
commitBatch: 3000 # 提交批大小
```
sql映射说明:
sql支持多表关联自由组合, 但是有一定的限制:
1. 主表不能为子查询语句
2. 只能使用left outer join即最左表一定要是主表
3. 关联从表如果是子查询不能有多张表
4. 主sql中不能有where查询条件(从表子查询中可以有where条件但是不推荐, 可能会造成数据同步的不一致, 比如修改了where条件中的字段内容)
5. 关联条件只允许主外键的'='操作不能出现其他常量判断比如: on a.role_id=b.id and b.statues=1
6. 关联条件必须要有一个字段出现在主查询语句中比如: on a.role_id=b.id 其中的 a.role_id 或者 b.id 必须出现在主select语句中
Elastic Search的mapping 属性与sql的查询值将一一对应(不支持 select *), 比如: select a.id as _id, a.name, a.email as _email from user, 其中name将映射到es mapping的name field, _email将
映射到mapping的_email field, 这里以别名(如果有别名)作为最终的映射字段. 这里的_id可以填写到配置文件的 _id: _id映射.
#### 5.2.1.单表映射索引示例sql:
```
select a.id as _id, a.name, a.role_id, a.c_time from user a
```
该sql对应的es mapping示例:
```
{
"mytest_user": {
"mappings": {
"_doc": {
"properties": {
"name": {
"type": "text"
},
"role_id": {
"type": "long"
},
"c_time": {
"type": "date"
}
}
}
}
}
}
```
#### 5.2.2.单表映射索引示例sql带函数或运算操作:
```
select a.id as _id, concat(a.name,'_test') as name, a.role_id+10000 as role_id, a.c_time from user a
```
函数字段后必须跟上别名, 该sql对应的es mapping示例:
```
{
"mytest_user": {
"mappings": {
"_doc": {
"properties": {
"name": {
"type": "text"
},
"role_id": {
"type": "long"
},
"c_time": {
"type": "date"
}
}
}
}
}
}
```
#### 5.2.3.多表映射(一对一, 多对一)索引示例sql:
```
select a.id as _id, a.name, a.role_id, b.role_name, a.c_time from user a
left join role b on b.id = a.role_id
```
注:这里join操作只能是left outer join, 第一张表必须为主表!!
该sql对应的es mapping示例:
```
{
"mytest_user": {
"mappings": {
"_doc": {
"properties": {
"name": {
"type": "text"
},
"role_id": {
"type": "long"
},
"role_name": {
"type": "text"
},
"c_time": {
"type": "date"
}
}
}
}
}
}
```
#### 5.2.4.多表映射(一对多)索引示例sql:
```
select a.id as _id, a.name, a.role_id, c.labels, a.c_time from user a
left join (select user_id, group_concat(label order by id desc separator ';') as labels from label
group by user_id) c on c.user_id=a.id
```
注:left join 后的子查询只允许一张表, 即子查询中不能再包含子查询或者关联!!
该sql对应的es mapping示例:
```
{
"mytest_user": {
"mappings": {
"_doc": {
"properties": {
"name": {
"type": "text"
},
"role_id": {
"type": "long"
},
"c_time": {
"type": "date"
},
"labels": {
"type": "text"
}
}
}
}
}
}
```
#### 5.2.5.其它类型的sql示例:
- geo type
```
select ... concat(IFNULL(a.latitude, 0), ',', IFNULL(a.longitude, 0)) AS location, ...
```
- 复合主键
```
select concat(a.id,'_',b.type) as _id, ... from user a left join role b on b.id=a.role_id
```
- 数组字段
```
select a.id as _id, a.name, a.role_id, c.labels, a.c_time from user a
left join (select user_id, group_concat(label order by id desc separator ';') as labels from label
group by user_id) c on c.user_id=a.id
```
配置中使用:
```
objFields:
labels: array:;
```
### 5.3 启动ES数据同步
#### 启动canal-adapter启动器
```
bin/startup.sh
```
#### 验证
1. 新增mysql mytest.user表的数据, 将会自动同步到es的mytest_user索引下面, 并会打出DML的log
2. 修改mysql mytest.role表的role_name, 将会自动同步es的mytest_user索引中的role_name数据
3. 新增或者修改mysql mytest.label表的label, 将会自动同步es的mytest_user索引中的labels数据
-41
View File
@@ -1,41 +0,0 @@
<?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>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.4-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.alibaba.otter</groupId>
<artifactId>client-adapter.common</artifactId>
<packaging>jar</packaging>
<name>canal client adapter common module for otter ${project.version}</name>
<dependencies>
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal.protocol</artifactId>
<version>1.1.4-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.9.4</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.9</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.19</version>
</dependency>
</dependencies>
</project>
@@ -1,70 +0,0 @@
package com.alibaba.otter.canal.client.adapter;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import com.alibaba.otter.canal.client.adapter.support.Dml;
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
import com.alibaba.otter.canal.client.adapter.support.SPI;
/**
* 外部适配器接口
*
* @author reweerma 2018-8-18 下午10:14:02
* @version 1.0.0
*/
@SPI("logger")
public interface OuterAdapter {
/**
* 外部适配器初始化接口
*
* @param configuration 外部适配器配置信息
* @param envProperties 环境变量的配置属性
*/
void init(OuterAdapterConfig configuration, Properties envProperties);
/**
* 往适配器中同步数据
*
* @param dmls 数据包
*/
void sync(List<Dml> dmls);
/**
* 外部适配器销毁接口
*/
void destroy();
/**
* Etl操作
*
* @param task 任务名, 对应配置名
* @param params etl筛选条件
*/
default EtlResult etl(String task, List<String> params) {
throw new UnsupportedOperationException("unsupported operation");
}
/**
* 计算总数
*
* @param task 任务名, 对应配置名
* @return 总数
*/
default Map<String, Object> count(String task) {
throw new UnsupportedOperationException("unsupported operation");
}
/**
* 通过task获取对应的destination
*
* @param task 任务名, 对应配置名
* @return destination
*/
default String getDestination(String task) {
return null;
}
}
@@ -1,155 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import org.springframework.util.PropertyPlaceholderHelper;
import org.springframework.util.StringUtils;
import com.alibaba.otter.canal.client.adapter.config.bind.PropertiesConfigurationFactory;
import com.alibaba.otter.canal.client.adapter.config.common.*;
/**
* 将yaml内容绑定到指定对象, 遵循spring yml的绑定规范
*
* @author reweerma 2019-2-1 上午9:14:02
* @version 1.0.0
*/
public class YmlConfigBinder {
/**
* 将当前内容绑定到指定对象
*
* @param content yml内容
* @param clazz 指定对象类型
* @return 对象
*/
public static <T> T bindYmlToObj(String content, Class<T> clazz) {
return bindYmlToObj(null, content, clazz, null);
}
/**
* 将当前内容绑定到指定对象并指定内容编码格式
*
* @param content yml内容
* @param clazz 指定对象类型
* @param charset yml内容编码格式
* @return 对象
*/
public static <T> T bindYmlToObj(String content, Class<T> clazz, String charset) {
return bindYmlToObj(null, content, clazz, charset);
}
/**
* 将当前内容指定前缀部分绑定到指定对象
*
* @param prefix 指定前缀
* @param content yml内容
* @param clazz 指定对象类型
* @return 对象
*/
public static <T> T bindYmlToObj(String prefix, String content, Class<T> clazz) {
return bindYmlToObj(prefix, content, clazz, null);
}
/**
* 将当前内容指定前缀部分绑定到指定对象并指定内容编码格式
*
* @param prefix 指定前缀
* @param content yml内容
* @param clazz 指定对象类型
* @param charset yml内容编码格式
* @return 对象
*/
public static <T> T bindYmlToObj(String prefix, String content, Class<T> clazz, String charset) {
return bindYmlToObj(prefix, content, clazz, charset, null);
}
/**
* 将当前内容指定前缀部分绑定到指定对象并用环境变量中的属性替换占位符, 例: 当前内容有属性 zkServers: ${zookeeper.servers}
* 在envProperties中有属性 zookeeper.servers:
* 192.168.0.1:2181,192.168.0.1:2181,192.168.0.1:2181 则当前内容 zkServers 会被替换为
* zkServers: 192.168.0.1:2181,192.168.0.1:2181,192.168.0.1:2181 注: 假设绑定的类中
* zkServers 属性是 List<String> 对象, 则会自动映射成List
*
* @param prefix 指定前缀
* @param content yml内容
* @param clazz 指定对象类型
* @param charset yml内容编码格式
* @return 对象
*/
public static <T> T bindYmlToObj(String prefix, String content, Class<T> clazz, String charset,
Properties baseProperties) {
try {
byte[] contentBytes;
if (charset == null) {
contentBytes = content.getBytes("UTF-8");
} else {
contentBytes = content.getBytes(charset);
}
YamlPropertySourceLoader propertySourceLoader = new YamlPropertySourceLoader();
Resource configResource = new ByteArrayResource(contentBytes);
PropertySource<?> propertySource = propertySourceLoader.load("manualBindConfig", configResource, null);
if (propertySource == null) {
return null;
}
Properties properties = new Properties();
Map<String, Object> propertiesRes = new LinkedHashMap<>();
if (!StringUtils.isEmpty(prefix) && !prefix.endsWith(".")) {
prefix = prefix + ".";
}
properties.putAll((Map<?, ?>) propertySource.getSource());
if (baseProperties != null) {
baseProperties.putAll(properties);
properties = baseProperties;
}
for (Map.Entry<?, ?> entry : ((Map<?, ?>) propertySource.getSource()).entrySet()) {
String key = (String) entry.getKey();
Object value = entry.getValue();
if (prefix != null) {
if (key != null && key.startsWith(prefix)) {
key = key.substring(prefix.length());
} else {
continue;
}
}
if (value instanceof String && ((String) value).contains("${") && ((String) value).contains("}")) {
PropertyPlaceholderHelper propertyPlaceholderHelper = new PropertyPlaceholderHelper("${", "}");
value = propertyPlaceholderHelper.replacePlaceholders((String) value, properties);
}
propertiesRes.put(key, value);
}
if (propertiesRes.isEmpty()) {
return null;
}
propertySource = new MapPropertySource(propertySource.getName(), propertiesRes);
T target = clazz.newInstance();
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(propertySource);
PropertiesConfigurationFactory<Object> factory = new PropertiesConfigurationFactory<Object>(target);
factory.setPropertySources(propertySources);
factory.setIgnoreInvalidFields(true);
factory.setIgnoreUnknownFields(true);
factory.bindPropertiesToTarget();
return target;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
@@ -1,97 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.bind;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* {@link PropertyNamePatternsMatcher} that matches when a property name exactly
* matches one of the given names, or starts with one of the given names
* followed by a delimiter. This implementation is optimized for frequent calls.
*
* @author Phillip Webb
* @since 1.2.0
*/
class DefaultPropertyNamePatternsMatcher implements PropertyNamePatternsMatcher {
private final char[] delimiters;
private final boolean ignoreCase;
private final String[] names;
protected DefaultPropertyNamePatternsMatcher(char[] delimiters, String... names){
this(delimiters, false, names);
}
protected DefaultPropertyNamePatternsMatcher(char[] delimiters, boolean ignoreCase, String... names){
this(delimiters, ignoreCase, new HashSet<String>(Arrays.asList(names)));
}
DefaultPropertyNamePatternsMatcher(char[] delimiters, boolean ignoreCase, Set<String> names){
this.delimiters = delimiters;
this.ignoreCase = ignoreCase;
this.names = names.toArray(new String[names.size()]);
}
@Override
public boolean matches(String propertyName) {
char[] propertyNameChars = propertyName.toCharArray();
boolean[] match = new boolean[this.names.length];
boolean noneMatched = true;
for (int i = 0; i < this.names.length; i++) {
if (this.names[i].length() <= propertyNameChars.length) {
match[i] = true;
noneMatched = false;
}
}
if (noneMatched) {
return false;
}
for (int charIndex = 0; charIndex < propertyNameChars.length; charIndex++) {
for (int nameIndex = 0; nameIndex < this.names.length; nameIndex++) {
if (match[nameIndex]) {
match[nameIndex] = false;
if (charIndex < this.names[nameIndex].length()) {
if (isCharMatch(this.names[nameIndex].charAt(charIndex), propertyNameChars[charIndex])) {
match[nameIndex] = true;
noneMatched = false;
}
} else {
char charAfter = propertyNameChars[this.names[nameIndex].length()];
if (isDelimiter(charAfter)) {
match[nameIndex] = true;
noneMatched = false;
}
}
}
}
if (noneMatched) {
return false;
}
}
for (int i = 0; i < match.length; i++) {
if (match[i]) {
return true;
}
}
return false;
}
private boolean isCharMatch(char c1, char c2) {
if (this.ignoreCase) {
return Character.toLowerCase(c1) == Character.toLowerCase(c2);
}
return c1 == c2;
}
private boolean isDelimiter(char c) {
for (char delimiter : this.delimiters) {
if (c == delimiter) {
return true;
}
}
return false;
}
}
@@ -1,31 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.bind;
import java.beans.PropertyEditorSupport;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* {@link PropertyNamePatternsMatcher} that matches when a property name exactly
* matches one of the given names, or starts with one of the given names
* followed by a delimiter. This implementation is optimized for frequent calls.
*
* @author Phillip Webb
* @since 1.2.0
*/
class InetAddressEditor extends PropertyEditorSupport {
@Override
public String getAsText() {
return ((InetAddress) getValue()).getHostAddress();
}
@Override
public void setAsText(String text) throws IllegalArgumentException {
try {
setValue(InetAddress.getByName(text));
} catch (UnknownHostException ex) {
throw new IllegalArgumentException("Cannot locate host", ex);
}
}
}
@@ -1,52 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.bind;
import org.springframework.beans.PropertyValue;
import com.alibaba.otter.canal.client.adapter.config.common.PropertySource;
/**
* A {@link PropertyValue} that can provide information about its origin.
*
* @author Andy Wilkinson
*/
class OriginCapablePropertyValue extends PropertyValue {
private static final String ATTRIBUTE_PROPERTY_ORIGIN = "propertyOrigin";
private final PropertyOrigin origin;
OriginCapablePropertyValue(PropertyValue propertyValue){
this(propertyValue.getName(),
propertyValue.getValue(),
(PropertyOrigin) propertyValue.getAttribute(ATTRIBUTE_PROPERTY_ORIGIN));
}
OriginCapablePropertyValue(String name, Object value, String originName, PropertySource<?> originSource){
this(name, value, new PropertyOrigin(originSource, originName));
}
OriginCapablePropertyValue(String name, Object value, PropertyOrigin origin){
super(name, value);
this.origin = origin;
setAttribute(ATTRIBUTE_PROPERTY_ORIGIN, origin);
}
public PropertyOrigin getOrigin() {
return this.origin;
}
@Override
public String toString() {
String name = (this.origin != null ? this.origin.getName() : this.getName());
String source = (this.origin.getSource() != null ? this.origin.getSource().getName() : "unknown");
return "'" + name + "' from '" + source + "'";
}
public static PropertyOrigin getOrigin(PropertyValue propertyValue) {
if (propertyValue instanceof OriginCapablePropertyValue) {
return ((OriginCapablePropertyValue) propertyValue).getOrigin();
}
return new OriginCapablePropertyValue(propertyValue).getOrigin();
}
}
@@ -1,27 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.bind;
import java.util.Collection;
import org.springframework.util.PatternMatchUtils;
/**
* {@link PropertyNamePatternsMatcher} that delegates to
* {@link PatternMatchUtils#simpleMatch(String[], String)}.
*
* @author Phillip Webb
* @since 1.2.0
*/
class PatternPropertyNamePatternsMatcher implements PropertyNamePatternsMatcher {
private final String[] patterns;
PatternPropertyNamePatternsMatcher(Collection<String> patterns){
this.patterns = (patterns != null ? patterns.toArray(new String[patterns.size()]) : new String[] {});
}
@Override
public boolean matches(String propertyName) {
return PatternMatchUtils.simpleMatch(this.patterns, propertyName);
}
}
@@ -1,356 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.bind;
import java.beans.PropertyDescriptor;
import java.util.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.support.ResourceEditorRegistrar;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.core.convert.ConversionService;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.validation.*;
import com.alibaba.otter.canal.client.adapter.config.common.PropertySources;
/**
* Validate some {@link Properties} (or optionally
* {@link org.springframework.core.env.PropertySources}) by binding them to an
* object of a specified type and then optionally running a {@link Validator}
* over it.
*
* @param <T> the target type
* @author Dave Syer
*/
public class PropertiesConfigurationFactory<T> implements FactoryBean<T>, ApplicationContextAware, MessageSourceAware, InitializingBean {
private static final char[] EXACT_DELIMITERS = { '_', '.', '[' };
private static final char[] TARGET_NAME_DELIMITERS = { '_', '.' };
private static final Log logger = LogFactory.getLog(PropertiesConfigurationFactory.class);
private boolean ignoreUnknownFields = true;
private boolean ignoreInvalidFields;
private boolean exceptionIfInvalid = true;
private PropertySources propertySources;
private final T target;
private Validator validator;
private ApplicationContext applicationContext;
private MessageSource messageSource;
private boolean hasBeenBound = false;
private boolean ignoreNestedProperties = false;
private String targetName;
private ConversionService conversionService;
private boolean resolvePlaceholders = true;
/**
* Create a new {@link PropertiesConfigurationFactory} instance.
*
* @param target the target object to bind too
* @see #PropertiesConfigurationFactory(Class)
*/
public PropertiesConfigurationFactory(T target){
Assert.notNull(target, "target must not be null");
this.target = target;
}
/**
* Create a new {@link PropertiesConfigurationFactory} instance.
*
* @param type the target type
* @see #PropertiesConfigurationFactory(Class)
*/
@SuppressWarnings("unchecked")
public PropertiesConfigurationFactory(Class<?> type){
Assert.notNull(type, "type must not be null");
this.target = (T) BeanUtils.instantiate(type);
}
/**
* Flag to disable binding of nested properties (i.e. those with period
* separators in their paths). Can be useful to disable this if the name prefix
* is empty and you don't want to ignore unknown fields.
*
* @param ignoreNestedProperties the flag to set (default false)
*/
public void setIgnoreNestedProperties(boolean ignoreNestedProperties) {
this.ignoreNestedProperties = ignoreNestedProperties;
}
/**
* Set whether to ignore unknown fields, that is, whether to ignore bind
* parameters that do not have corresponding fields in the target object.
* <p>
* Default is "true". Turn this off to enforce that all bind parameters must
* have a matching field in the target object.
*
* @param ignoreUnknownFields if unknown fields should be ignored
*/
public void setIgnoreUnknownFields(boolean ignoreUnknownFields) {
this.ignoreUnknownFields = ignoreUnknownFields;
}
/**
* Set whether to ignore invalid fields, that is, whether to ignore bind
* parameters that have corresponding fields in the target object which are not
* accessible (for example because of null values in the nested path).
* <p>
* Default is "false". Turn this on to ignore bind parameters for nested objects
* in non-existing parts of the target object graph.
*
* @param ignoreInvalidFields if invalid fields should be ignored
*/
public void setIgnoreInvalidFields(boolean ignoreInvalidFields) {
this.ignoreInvalidFields = ignoreInvalidFields;
}
/**
* Set the target name.
*
* @param targetName the target name
*/
public void setTargetName(String targetName) {
this.targetName = targetName;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
/**
* Set the message source.
*
* @param messageSource the message source
*/
@Override
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
/**
* Set the property sources.
*
* @param propertySources the property sources
*/
public void setPropertySources(PropertySources propertySources) {
this.propertySources = propertySources;
}
/**
* Set the conversion service.
*
* @param conversionService the conversion service
*/
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
/**
* Set the validator.
*
* @param validator the validator
*/
public void setValidator(Validator validator) {
this.validator = validator;
}
/**
* Set a flag to indicate that an exception should be raised if a Validator is
* available and validation fails.
*
* @param exceptionIfInvalid the flag to set
* @deprecated as of 1.5, do not specify a {@link Validator} if validation
* should not occur
*/
@Deprecated
public void setExceptionIfInvalid(boolean exceptionIfInvalid) {
this.exceptionIfInvalid = exceptionIfInvalid;
}
/**
* Flag to indicate that placeholders should be replaced during binding. Default
* is true.
*
* @param resolvePlaceholders flag value
*/
public void setResolvePlaceholders(boolean resolvePlaceholders) {
this.resolvePlaceholders = resolvePlaceholders;
}
@Override
public void afterPropertiesSet() throws Exception {
bindPropertiesToTarget();
}
@Override
public Class<?> getObjectType() {
if (this.target == null) {
return Object.class;
}
return this.target.getClass();
}
@Override
public boolean isSingleton() {
return true;
}
@Override
public T getObject() throws Exception {
if (!this.hasBeenBound) {
bindPropertiesToTarget();
}
return this.target;
}
public void bindPropertiesToTarget() throws BindException {
Assert.state(this.propertySources != null, "PropertySources should not be null");
try {
if (logger.isTraceEnabled()) {
logger.trace("Property Sources: " + this.propertySources);
}
this.hasBeenBound = true;
doBindPropertiesToTarget();
} catch (BindException ex) {
if (this.exceptionIfInvalid) {
throw ex;
}
logger.error("Failed to load Properties validation bean. " + "Your Properties may be invalid.", ex);
}
}
private void doBindPropertiesToTarget() throws BindException {
RelaxedDataBinder dataBinder = (this.targetName != null ? new RelaxedDataBinder(this.target,
this.targetName) : new RelaxedDataBinder(this.target));
if (this.validator != null && this.validator.supports(dataBinder.getTarget().getClass())) {
dataBinder.setValidator(this.validator);
}
if (this.conversionService != null) {
dataBinder.setConversionService(this.conversionService);
}
dataBinder.setAutoGrowCollectionLimit(Integer.MAX_VALUE);
dataBinder.setIgnoreNestedProperties(this.ignoreNestedProperties);
dataBinder.setIgnoreInvalidFields(this.ignoreInvalidFields);
dataBinder.setIgnoreUnknownFields(this.ignoreUnknownFields);
customizeBinder(dataBinder);
if (this.applicationContext != null) {
ResourceEditorRegistrar resourceEditorRegistrar = new ResourceEditorRegistrar(this.applicationContext,
this.applicationContext.getEnvironment());
resourceEditorRegistrar.registerCustomEditors(dataBinder);
}
Iterable<String> relaxedTargetNames = getRelaxedTargetNames();
Set<String> names = getNames(relaxedTargetNames);
PropertyValues propertyValues = getPropertySourcesPropertyValues(names, relaxedTargetNames);
dataBinder.bind(propertyValues);
if (this.validator != null) {
dataBinder.validate();
}
checkForBindingErrors(dataBinder);
}
private Iterable<String> getRelaxedTargetNames() {
return (this.target != null
&& StringUtils.hasLength(this.targetName) ? new RelaxedNames(this.targetName) : null);
}
private Set<String> getNames(Iterable<String> prefixes) {
Set<String> names = new LinkedHashSet<String>();
if (this.target != null) {
PropertyDescriptor[] descriptors = BeanUtils.getPropertyDescriptors(this.target.getClass());
for (PropertyDescriptor descriptor : descriptors) {
String name = descriptor.getName();
if (!name.equals("class")) {
RelaxedNames relaxedNames = RelaxedNames.forCamelCase(name);
if (prefixes == null) {
for (String relaxedName : relaxedNames) {
names.add(relaxedName);
}
} else {
for (String prefix : prefixes) {
for (String relaxedName : relaxedNames) {
names.add(prefix + "." + relaxedName);
names.add(prefix + "_" + relaxedName);
}
}
}
}
}
}
return names;
}
private PropertyValues getPropertySourcesPropertyValues(Set<String> names, Iterable<String> relaxedTargetNames) {
PropertyNamePatternsMatcher includes = getPropertyNamePatternsMatcher(names, relaxedTargetNames);
return new PropertySourcesPropertyValues(this.propertySources, names, includes, this.resolvePlaceholders);
}
private PropertyNamePatternsMatcher getPropertyNamePatternsMatcher(Set<String> names,
Iterable<String> relaxedTargetNames) {
if (this.ignoreUnknownFields && !isMapTarget()) {
// Since unknown fields are ignored we can filter them out early to save
// unnecessary calls to the PropertySource.
return new DefaultPropertyNamePatternsMatcher(EXACT_DELIMITERS, true, names);
}
if (relaxedTargetNames != null) {
// We can filter properties to those starting with the target name, but
// we can't do a complete filter since we need to trigger the
// unknown fields check
Set<String> relaxedNames = new HashSet<String>();
for (String relaxedTargetName : relaxedTargetNames) {
relaxedNames.add(relaxedTargetName);
}
return new DefaultPropertyNamePatternsMatcher(TARGET_NAME_DELIMITERS, true, relaxedNames);
}
// Not ideal, we basically can't filter anything
return PropertyNamePatternsMatcher.ALL;
}
private boolean isMapTarget() {
return this.target != null && Map.class.isAssignableFrom(this.target.getClass());
}
private void checkForBindingErrors(RelaxedDataBinder dataBinder) throws BindException {
BindingResult errors = dataBinder.getBindingResult();
if (errors.hasErrors()) {
logger.error("Properties configuration failed validation");
for (ObjectError error : errors.getAllErrors()) {
logger.error(this.messageSource != null ? this.messageSource.getMessage(error, Locale.getDefault())
+ " (" + error + ")" : error);
}
if (this.exceptionIfInvalid) {
throw new BindException(errors);
}
}
}
/**
* Customize the data binder.
*
* @param dataBinder the data binder that will be used to bind and validate
*/
protected void customizeBinder(DataBinder dataBinder) {
}
}
@@ -1,38 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.bind;
/**
* Strategy interface used to check if a property name matches specific
* criteria.
*
* @author Phillip Webb
* @since 1.2.0
*/
interface PropertyNamePatternsMatcher {
PropertyNamePatternsMatcher ALL = new PropertyNamePatternsMatcher() {
@Override
public boolean matches(String propertyName) {
return true;
}
};
PropertyNamePatternsMatcher NONE = new PropertyNamePatternsMatcher() {
@Override
public boolean matches(String propertyName) {
return false;
}
};
/**
* Return {@code true} of the property name matches.
*
* @param propertyName the property name
* @return {@code true} if the property name matches
*/
boolean matches(String propertyName);
}
@@ -1,30 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.bind;
import com.alibaba.otter.canal.client.adapter.config.common.PropertySource;
/**
* The origin of a property, specifically its source and its name before any
* prefix was removed.
*
* @author Andy Wilkinson
* @since 1.3.0
*/
public class PropertyOrigin {
private final PropertySource<?> source;
private final String name;
PropertyOrigin(PropertySource<?> source, String name){
this.name = name;
this.source = source;
}
public PropertySource<?> getSource() {
return this.source;
}
public String getName() {
return this.name;
}
}
@@ -1,164 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.bind;
import org.springframework.core.convert.ConversionException;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.AbstractPropertyResolver;
import org.springframework.core.env.PropertyResolver;
import org.springframework.util.ClassUtils;
import com.alibaba.otter.canal.client.adapter.config.common.PropertySource;
import com.alibaba.otter.canal.client.adapter.config.common.PropertySources;
/**
* {@link PropertyResolver} implementation that resolves property values against
* an underlying set of {@link PropertySources}.
*
* @author Chris Beams
* @author Juergen Hoeller
* @see PropertySource
* @see PropertySources
* @see AbstractEnvironment
* @since 3.1
*/
public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
private final PropertySources propertySources;
/**
* Create a new resolver against the given property sources.
*
* @param propertySources the set of {@link PropertySource} objects to use
*/
public PropertySourcesPropertyResolver(PropertySources propertySources){
this.propertySources = propertySources;
}
@Override
public boolean containsProperty(String key) {
if (this.propertySources != null) {
for (PropertySource<?> propertySource : this.propertySources) {
if (propertySource.containsProperty(key)) {
return true;
}
}
}
return false;
}
@Override
public String getProperty(String key) {
return getProperty(key, String.class, true);
}
@Override
public <T> T getProperty(String key, Class<T> targetValueType) {
return getProperty(key, targetValueType, true);
}
@Override
protected String getPropertyAsRawString(String key) {
return getProperty(key, String.class, false);
}
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
if (this.propertySources != null) {
for (PropertySource<?> propertySource : this.propertySources) {
if (logger.isTraceEnabled()) {
logger
.trace("Searching for key '" + key + "' in PropertySource '" + propertySource.getName() + "'");
}
Object value = propertySource.getProperty(key);
if (value != null) {
if (resolveNestedPlaceholders && value instanceof String) {
value = resolveNestedPlaceholders((String) value);
}
logKeyFound(key, propertySource, value);
return convertValueIfNecessary(value, targetValueType);
}
}
}
if (logger.isDebugEnabled()) {
logger.debug("Could not find key '" + key + "' in any property source");
}
return null;
}
@Deprecated
public <T> Class<T> getPropertyAsClass(String key, Class<T> targetValueType) {
if (this.propertySources != null) {
for (PropertySource<?> propertySource : this.propertySources) {
if (logger.isTraceEnabled()) {
logger.trace(String.format("Searching for key '%s' in [%s]", key, propertySource.getName()));
}
Object value = propertySource.getProperty(key);
if (value != null) {
logKeyFound(key, propertySource, value);
Class<?> clazz;
if (value instanceof String) {
try {
clazz = ClassUtils.forName((String) value, null);
} catch (Exception ex) {
throw new PropertySourcesPropertyResolver.ClassConversionException((String) value,
targetValueType,
ex);
}
} else if (value instanceof Class) {
clazz = (Class<?>) value;
} else {
clazz = value.getClass();
}
if (!targetValueType.isAssignableFrom(clazz)) {
throw new PropertySourcesPropertyResolver.ClassConversionException(clazz, targetValueType);
}
@SuppressWarnings("unchecked")
Class<T> targetClass = (Class<T>) clazz;
return targetClass;
}
}
}
if (logger.isDebugEnabled()) {
logger.debug(String.format("Could not find key '%s' in any property source", key));
}
return null;
}
/**
* Log the given key as found in the given {@link PropertySource}, resulting in
* the given value.
* <p>
* The default implementation writes a debug log message with key and source. As
* of 4.3.3, this does not log the value anymore in order to avoid accidental
* logging of sensitive settings. Subclasses may override this method to change
* the log level and/or log message, including the property's value if desired.
*
* @param key the key found
* @param propertySource the {@code PropertySource} that the key has been found
* in
* @param value the corresponding value
* @since 4.3.1
*/
protected void logKeyFound(String key, PropertySource<?> propertySource, Object value) {
if (logger.isDebugEnabled()) {
logger.debug("Found key '" + key + "' in PropertySource '" + propertySource.getName()
+ "' with value of type " + value.getClass().getSimpleName());
}
}
@SuppressWarnings("serial")
@Deprecated
private static class ClassConversionException extends ConversionException {
public ClassConversionException(Class<?> actual, Class<?> expected){
super(String
.format("Actual type %s is not assignable to expected type %s", actual.getName(), expected.getName()));
}
public ClassConversionException(String actual, Class<?> expected, Exception ex){
super(
String
.format("Could not find/load class %s during attempt to convert to %s", actual, expected.getName()),
ex);
}
}
}
@@ -1,233 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.bind;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.PropertyValues;
import org.springframework.util.Assert;
import org.springframework.validation.DataBinder;
import com.alibaba.otter.canal.client.adapter.config.common.CompositePropertySource;
import com.alibaba.otter.canal.client.adapter.config.common.EnumerablePropertySource;
import com.alibaba.otter.canal.client.adapter.config.common.PropertySource;
import com.alibaba.otter.canal.client.adapter.config.common.PropertySources;
/**
* A {@link PropertyValues} implementation backed by a {@link PropertySources},
* bridging the two abstractions and allowing (for instance) a regular
* {@link DataBinder} to be used with the latter.
*
* @author Dave Syer
* @author Phillip Webb
*/
public class PropertySourcesPropertyValues implements PropertyValues {
private static final Pattern COLLECTION_PROPERTY = Pattern
.compile("\\[(\\d+)\\](\\.\\S+)?");
private final PropertySources propertySources;
private final Collection<String> nonEnumerableFallbackNames;
private final PropertyNamePatternsMatcher includes;
private final Map<String, PropertyValue> propertyValues = new LinkedHashMap<String, PropertyValue>();
private final ConcurrentHashMap<String, PropertySource<?>> collectionOwners = new ConcurrentHashMap<String, PropertySource<?>>();
private final boolean resolvePlaceholders;
/**
* Create a new PropertyValues from the given PropertySources.
*
* @param propertySources a PropertySources instance
*/
public PropertySourcesPropertyValues(PropertySources propertySources){
this(propertySources, true);
}
/**
* Create a new PropertyValues from the given PropertySources that will
* optionally resolve placeholders.
*
* @param propertySources a PropertySources instance
* @param resolvePlaceholders {@code true} if placeholders should be resolved.
* @since 1.5.2
*/
public PropertySourcesPropertyValues(PropertySources propertySources, boolean resolvePlaceholders){
this(propertySources, (Collection<String>) null, PropertyNamePatternsMatcher.ALL, resolvePlaceholders);
}
/**
* Create a new PropertyValues from the given PropertySources.
*
* @param propertySources a PropertySources instance
* @param includePatterns property name patterns to include from system
* properties and environment variables
* @param nonEnumerableFallbackNames the property names to try in lieu of an
* {@link EnumerablePropertySource}.
*/
public PropertySourcesPropertyValues(PropertySources propertySources, Collection<String> includePatterns,
Collection<String> nonEnumerableFallbackNames){
this(propertySources,
nonEnumerableFallbackNames,
new PatternPropertyNamePatternsMatcher(includePatterns),
true);
}
/**
* Create a new PropertyValues from the given PropertySources.
*
* @param propertySources a PropertySources instance
* @param nonEnumerableFallbackNames the property names to try in lieu of an
* {@link EnumerablePropertySource}.
* @param includes the property name patterns to include
* @param resolvePlaceholders flag to indicate the placeholders should be
* resolved
*/
PropertySourcesPropertyValues(PropertySources propertySources, Collection<String> nonEnumerableFallbackNames,
PropertyNamePatternsMatcher includes, boolean resolvePlaceholders){
Assert.notNull(propertySources, "PropertySources must not be null");
Assert.notNull(includes, "Includes must not be null");
this.propertySources = propertySources;
this.nonEnumerableFallbackNames = nonEnumerableFallbackNames;
this.includes = includes;
this.resolvePlaceholders = resolvePlaceholders;
PropertySourcesPropertyResolver resolver = new PropertySourcesPropertyResolver(propertySources);
for (PropertySource<?> source : propertySources) {
processPropertySource(source, resolver);
}
}
private void processPropertySource(PropertySource<?> source, PropertySourcesPropertyResolver resolver) {
if (source instanceof CompositePropertySource) {
processCompositePropertySource((CompositePropertySource) source, resolver);
} else if (source instanceof EnumerablePropertySource) {
processEnumerablePropertySource((EnumerablePropertySource<?>) source, resolver, this.includes);
} else {
processNonEnumerablePropertySource(source, resolver);
}
}
private void processCompositePropertySource(CompositePropertySource source,
PropertySourcesPropertyResolver resolver) {
for (PropertySource<?> nested : source.getPropertySources()) {
processPropertySource(nested, resolver);
}
}
private void processEnumerablePropertySource(EnumerablePropertySource<?> source,
PropertySourcesPropertyResolver resolver,
PropertyNamePatternsMatcher includes) {
if (source.getPropertyNames().length > 0) {
for (String propertyName : source.getPropertyNames()) {
if (includes.matches(propertyName)) {
Object value = getEnumerableProperty(source, resolver, propertyName);
putIfAbsent(propertyName, value, source);
}
}
}
}
private Object getEnumerableProperty(EnumerablePropertySource<?> source, PropertySourcesPropertyResolver resolver,
String propertyName) {
try {
if (this.resolvePlaceholders) {
return resolver.getProperty(propertyName, Object.class);
}
} catch (RuntimeException ex) {
// Probably could not resolve placeholders, ignore it here
}
return source.getProperty(propertyName);
}
private void processNonEnumerablePropertySource(PropertySource<?> source,
PropertySourcesPropertyResolver resolver) {
// We can only do exact matches for non-enumerable property names, but
// that's better than nothing...
if (this.nonEnumerableFallbackNames == null) {
return;
}
for (String propertyName : this.nonEnumerableFallbackNames) {
if (!source.containsProperty(propertyName)) {
continue;
}
Object value = null;
try {
value = resolver.getProperty(propertyName, Object.class);
} catch (RuntimeException ex) {
// Probably could not convert to Object, weird, but ignorable
}
if (value == null) {
value = source.getProperty(propertyName.toUpperCase(Locale.ENGLISH));
}
putIfAbsent(propertyName, value, source);
}
}
@Override
public PropertyValue[] getPropertyValues() {
Collection<PropertyValue> values = this.propertyValues.values();
return values.toArray(new PropertyValue[values.size()]);
}
@Override
public PropertyValue getPropertyValue(String propertyName) {
PropertyValue propertyValue = this.propertyValues.get(propertyName);
if (propertyValue != null) {
return propertyValue;
}
for (PropertySource<?> source : this.propertySources) {
Object value = source.getProperty(propertyName);
propertyValue = putIfAbsent(propertyName, value, source);
if (propertyValue != null) {
return propertyValue;
}
}
return null;
}
private PropertyValue putIfAbsent(String propertyName, Object value, PropertySource<?> source) {
if (value != null && !this.propertyValues.containsKey(propertyName)) {
PropertySource<?> collectionOwner = this.collectionOwners
.putIfAbsent(COLLECTION_PROPERTY.matcher(propertyName).replaceAll("[]"), source);
if (collectionOwner == null || collectionOwner == source) {
PropertyValue propertyValue = new OriginCapablePropertyValue(propertyName, value, propertyName, source);
this.propertyValues.put(propertyName, propertyValue);
return propertyValue;
}
}
return null;
}
@Override
public PropertyValues changesSince(PropertyValues old) {
MutablePropertyValues changes = new MutablePropertyValues();
// for each property value in the new set
for (PropertyValue newValue : getPropertyValues()) {
// if there wasn't an old one, add it
PropertyValue oldValue = old.getPropertyValue(newValue.getName());
if (oldValue == null || !oldValue.equals(newValue)) {
changes.addPropertyValue(newValue);
}
}
return changes;
}
@Override
public boolean contains(String propertyName) {
return getPropertyValue(propertyName) != null;
}
@Override
public boolean isEmpty() {
return this.propertyValues.isEmpty();
}
}
@@ -1,127 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.bind;
import java.util.EnumSet;
import java.util.Locale;
import java.util.Set;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.util.Assert;
/**
* Internal {@link ConversionService} used by {@link RelaxedDataBinder} to
* support additional relaxed conversion.
*
* @author Phillip Webb
* @author Stephane Nicoll
* @since 1.1.0
*/
class RelaxedConversionService implements ConversionService {
private final ConversionService conversionService;
private final GenericConversionService additionalConverters;
/**
* Create a new {@link RelaxedConversionService} instance.
*
* @param conversionService and option root conversion service
*/
RelaxedConversionService(ConversionService conversionService){
this.conversionService = conversionService;
this.additionalConverters = new GenericConversionService();
DefaultConversionService.addCollectionConverters(this.additionalConverters);
this.additionalConverters
.addConverterFactory(new RelaxedConversionService.StringToEnumIgnoringCaseConverterFactory());
this.additionalConverters.addConverter(new StringToCharArrayConverter());
}
@Override
public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
return (this.conversionService != null && this.conversionService.canConvert(sourceType, targetType))
|| this.additionalConverters.canConvert(sourceType, targetType);
}
@Override
public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {
return (this.conversionService != null && this.conversionService.canConvert(sourceType, targetType))
|| this.additionalConverters.canConvert(sourceType, targetType);
}
@Override
@SuppressWarnings("unchecked")
public <T> T convert(Object source, Class<T> targetType) {
Assert.notNull(targetType, "The targetType to convert to cannot be null");
return (T) convert(source, TypeDescriptor.forObject(source), TypeDescriptor.valueOf(targetType));
}
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (this.conversionService != null) {
try {
return this.conversionService.convert(source, sourceType, targetType);
} catch (ConversionFailedException ex) {
// Ignore and try the additional converters
}
}
return this.additionalConverters.convert(source, sourceType, targetType);
}
/**
* Clone of Spring's package private StringToEnumConverterFactory, but ignoring
* the case of the source.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private static class StringToEnumIgnoringCaseConverterFactory implements ConverterFactory<String, Enum> {
@Override
public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
Class<?> enumType = targetType;
while (enumType != null && !enumType.isEnum()) {
enumType = enumType.getSuperclass();
}
Assert.notNull(enumType, "The target type " + targetType.getName() + " does not refer to an enum");
return new RelaxedConversionService.StringToEnumIgnoringCaseConverterFactory.StringToEnum(enumType);
}
private static class StringToEnum<T extends Enum> implements Converter<String, T> {
private final Class<T> enumType;
StringToEnum(Class<T> enumType){
this.enumType = enumType;
}
@Override
public T convert(String source) {
if (source.isEmpty()) {
// It's an empty enum identifier: reset the enum value to null.
return null;
}
source = source.trim();
for (T candidate : (Set<T>) EnumSet.allOf(this.enumType)) {
RelaxedNames names = new RelaxedNames(
candidate.name().replace('_', '-').toLowerCase(Locale.ENGLISH));
for (String name : names) {
if (name.equals(source)) {
return candidate;
}
}
if (candidate.name().equalsIgnoreCase(source)) {
return candidate;
}
}
throw new IllegalArgumentException(
"No enum constant " + this.enumType.getCanonicalName() + "." + source);
}
}
}
}
@@ -1,729 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.bind;
import java.beans.PropertyEditor;
import java.net.InetAddress;
import java.util.*;
import org.springframework.beans.*;
import org.springframework.beans.propertyeditors.FileEditor;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.validation.AbstractPropertyBindingResult;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.DataBinder;
/**
* Binder implementation that allows caller to bind to maps and also allows
* property names to match a bit loosely (if underscores or dashes are removed
* and replaced with camel case for example).
*
* @author Dave Syer
* @author Phillip Webb
* @author Stephane Nicoll
* @author Andy Wilkinson
* @see RelaxedNames
*/
public class RelaxedDataBinder extends DataBinder {
private static final Set<Class<?>> EXCLUDED_EDITORS;
static {
Set<Class<?>> excluded = new HashSet<Class<?>>();
excluded.add(FileEditor.class);
EXCLUDED_EDITORS = Collections.unmodifiableSet(excluded);
}
private static final Object BLANK = new Object();
private String namePrefix;
private boolean ignoreNestedProperties;
private MultiValueMap<String, String> nameAliases = new LinkedMultiValueMap<String, String>();
/**
* Create a new {@link RelaxedDataBinder} instance.
*
* @param target the target into which properties are bound
*/
public RelaxedDataBinder(Object target){
super(wrapTarget(target));
}
/**
* Create a new {@link RelaxedDataBinder} instance.
*
* @param target the target into which properties are bound
* @param namePrefix An optional prefix to be used when reading properties
*/
public RelaxedDataBinder(Object target, String namePrefix){
super(wrapTarget(target), (StringUtils.hasLength(namePrefix) ? namePrefix : DEFAULT_OBJECT_NAME));
this.namePrefix = cleanNamePrefix(namePrefix);
}
private String cleanNamePrefix(String namePrefix) {
if (!StringUtils.hasLength(namePrefix)) {
return null;
}
return (namePrefix.endsWith(".") ? namePrefix : namePrefix + ".");
}
/**
* Flag to disable binding of nested properties (i.e. those with period
* separators in their paths). Can be useful to disable this if the name prefix
* is empty and you don't want to ignore unknown fields.
*
* @param ignoreNestedProperties the flag to set (default false)
*/
public void setIgnoreNestedProperties(boolean ignoreNestedProperties) {
this.ignoreNestedProperties = ignoreNestedProperties;
}
/**
* Set name aliases.
*
* @param aliases a map of property name to aliases
*/
public void setNameAliases(Map<String, List<String>> aliases) {
this.nameAliases = new LinkedMultiValueMap<String, String>(aliases);
}
/**
* Add aliases to the {@link DataBinder}.
*
* @param name the property name to alias
* @param alias aliases for the property names
* @return this instance
*/
public RelaxedDataBinder withAlias(String name, String... alias) {
for (String value : alias) {
this.nameAliases.add(name, value);
}
return this;
}
@Override
protected void doBind(MutablePropertyValues propertyValues) {
super.doBind(modifyProperties(propertyValues, getTarget()));
}
/**
* Modify the property values so that period separated property paths are valid
* for map keys. Also creates new maps for properties of map type that are null
* (assuming all maps are potentially nested). The standard bracket {@code[...]}
* dereferencing is also accepted.
*
* @param propertyValues the property values
* @param target the target object
* @return modified property values
*/
private MutablePropertyValues modifyProperties(MutablePropertyValues propertyValues, Object target) {
propertyValues = getPropertyValuesForNamePrefix(propertyValues);
if (target instanceof RelaxedDataBinder.MapHolder) {
propertyValues = addMapPrefix(propertyValues);
}
BeanWrapper wrapper = new BeanWrapperImpl(target);
wrapper.setConversionService(new RelaxedConversionService(getConversionService()));
wrapper.setAutoGrowNestedPaths(true);
List<PropertyValue> sortedValues = new ArrayList<PropertyValue>();
Set<String> modifiedNames = new HashSet<String>();
List<String> sortedNames = getSortedPropertyNames(propertyValues);
for (String name : sortedNames) {
PropertyValue propertyValue = propertyValues.getPropertyValue(name);
PropertyValue modifiedProperty = modifyProperty(wrapper, propertyValue);
if (modifiedNames.add(modifiedProperty.getName())) {
sortedValues.add(modifiedProperty);
}
}
return new MutablePropertyValues(sortedValues);
}
private List<String> getSortedPropertyNames(MutablePropertyValues propertyValues) {
List<String> names = new LinkedList<String>();
for (PropertyValue propertyValue : propertyValues.getPropertyValueList()) {
names.add(propertyValue.getName());
}
sortPropertyNames(names);
return names;
}
/**
* Sort by name so that parent properties get processed first (e.g. 'foo.bar'
* before 'foo.bar.spam'). Don't use Collections.sort() because the order might
* be significant for other property names (it shouldn't be but who knows what
* people might be relying on, e.g. HSQL has a JDBCXADataSource where
* "databaseName" is a synonym for "url").
*
* @param names the names to sort
*/
private void sortPropertyNames(List<String> names) {
for (String name : new ArrayList<String>(names)) {
int propertyIndex = names.indexOf(name);
RelaxedDataBinder.BeanPath path = new RelaxedDataBinder.BeanPath(name);
for (String prefix : path.prefixes()) {
int prefixIndex = names.indexOf(prefix);
if (prefixIndex >= propertyIndex) {
// The child property has a parent in the list in the wrong order
names.remove(name);
names.add(prefixIndex, name);
}
}
}
}
private MutablePropertyValues addMapPrefix(MutablePropertyValues propertyValues) {
MutablePropertyValues rtn = new MutablePropertyValues();
for (PropertyValue pv : propertyValues.getPropertyValues()) {
rtn.add("map." + pv.getName(), pv.getValue());
}
return rtn;
}
private MutablePropertyValues getPropertyValuesForNamePrefix(MutablePropertyValues propertyValues) {
if (!StringUtils.hasText(this.namePrefix) && !this.ignoreNestedProperties) {
return propertyValues;
}
MutablePropertyValues rtn = new MutablePropertyValues();
for (PropertyValue value : propertyValues.getPropertyValues()) {
String name = value.getName();
for (String prefix : new RelaxedNames(stripLastDot(this.namePrefix))) {
for (String separator : new String[] { ".", "_" }) {
String candidate = (StringUtils.hasLength(prefix) ? prefix + separator : prefix);
if (name.startsWith(candidate)) {
name = name.substring(candidate.length());
if (!(this.ignoreNestedProperties && name.contains("."))) {
PropertyOrigin propertyOrigin = OriginCapablePropertyValue.getOrigin(value);
rtn.addPropertyValue(
new OriginCapablePropertyValue(name, value.getValue(), propertyOrigin));
}
}
}
}
}
return rtn;
}
private String stripLastDot(String string) {
if (StringUtils.hasLength(string) && string.endsWith(".")) {
string = string.substring(0, string.length() - 1);
}
return string;
}
private PropertyValue modifyProperty(BeanWrapper target, PropertyValue propertyValue) {
String name = propertyValue.getName();
String normalizedName = normalizePath(target, name);
if (!normalizedName.equals(name)) {
return new PropertyValue(normalizedName, propertyValue.getValue());
}
return propertyValue;
}
/**
* Normalize a bean property path to a format understood by a BeanWrapper. This
* is used so that
* <ul>
* <li>Fuzzy matching can be employed for bean property names</li>
* <li>Period separators can be used instead of indexing ([...]) for map
* keys</li>
* </ul>
*
* @param wrapper a bean wrapper for the object to bind
* @param path the bean path to bind
* @return a transformed path with correct bean wrapper syntax
*/
protected String normalizePath(BeanWrapper wrapper, String path) {
return initializePath(wrapper, new RelaxedDataBinder.BeanPath(path), 0);
}
@Override
protected AbstractPropertyBindingResult createBeanPropertyBindingResult() {
return new RelaxedDataBinder.RelaxedBeanPropertyBindingResult(getTarget(),
getObjectName(),
isAutoGrowNestedPaths(),
getAutoGrowCollectionLimit(),
getConversionService());
}
private String initializePath(BeanWrapper wrapper, RelaxedDataBinder.BeanPath path, int index) {
String prefix = path.prefix(index);
String key = path.name(index);
if (path.isProperty(index)) {
key = getActualPropertyName(wrapper, prefix, key);
path.rename(index, key);
}
if (path.name(++index) == null) {
return path.toString();
}
String name = path.prefix(index);
TypeDescriptor descriptor = wrapper.getPropertyTypeDescriptor(name);
if (descriptor == null || descriptor.isMap()) {
if (isMapValueStringType(descriptor) || isBlanked(wrapper, name, path.name(index))) {
path.collapseKeys(index);
}
path.mapIndex(index);
extendMapIfNecessary(wrapper, path, index);
} else if (descriptor.isCollection()) {
extendCollectionIfNecessary(wrapper, path, index);
} else if (descriptor.getType().equals(Object.class)) {
if (isBlanked(wrapper, name, path.name(index))) {
path.collapseKeys(index);
}
path.mapIndex(index);
if (path.isLastNode(index)) {
wrapper.setPropertyValue(path.toString(), BLANK);
} else {
String next = path.prefix(index + 1);
if (wrapper.getPropertyValue(next) == null) {
wrapper.setPropertyValue(next, new LinkedHashMap<String, Object>());
}
}
}
return initializePath(wrapper, path, index);
}
private boolean isMapValueStringType(TypeDescriptor descriptor) {
if (descriptor == null || descriptor.getMapValueTypeDescriptor() == null) {
return false;
}
if (Properties.class.isAssignableFrom(descriptor.getObjectType())) {
// Properties is declared as Map<Object,Object> but we know it's really
// Map<String,String>
return true;
}
Class<?> valueType = descriptor.getMapValueTypeDescriptor().getObjectType();
return (valueType != null && CharSequence.class.isAssignableFrom(valueType));
}
@SuppressWarnings("rawtypes")
private boolean isBlanked(BeanWrapper wrapper, String propertyName, String key) {
Object value = (wrapper.isReadableProperty(propertyName) ? wrapper.getPropertyValue(propertyName) : null);
if (value instanceof Map) {
if (((Map) value).get(key) == BLANK) {
return true;
}
}
return false;
}
private void extendCollectionIfNecessary(BeanWrapper wrapper, RelaxedDataBinder.BeanPath path, int index) {
String name = path.prefix(index);
TypeDescriptor elementDescriptor = wrapper.getPropertyTypeDescriptor(name).getElementTypeDescriptor();
if (!elementDescriptor.isMap() && !elementDescriptor.isCollection()
&& !elementDescriptor.getType().equals(Object.class)) {
return;
}
Object extend = new LinkedHashMap<String, Object>();
if (!elementDescriptor.isMap() && path.isArrayIndex(index)) {
extend = new ArrayList<Object>();
}
wrapper.setPropertyValue(path.prefix(index + 1), extend);
}
private void extendMapIfNecessary(BeanWrapper wrapper, RelaxedDataBinder.BeanPath path, int index) {
String name = path.prefix(index);
TypeDescriptor parent = wrapper.getPropertyTypeDescriptor(name);
if (parent == null) {
return;
}
TypeDescriptor descriptor = parent.getMapValueTypeDescriptor();
if (descriptor == null) {
descriptor = TypeDescriptor.valueOf(Object.class);
}
if (!descriptor.isMap() && !descriptor.isCollection() && !descriptor.getType().equals(Object.class)) {
return;
}
String extensionName = path.prefix(index + 1);
if (wrapper.isReadableProperty(extensionName)) {
Object currentValue = wrapper.getPropertyValue(extensionName);
if ((descriptor.isCollection() && currentValue instanceof Collection)
|| (!descriptor.isCollection() && currentValue instanceof Map)) {
return;
}
}
Object extend = new LinkedHashMap<String, Object>();
if (descriptor.isCollection()) {
extend = new ArrayList<Object>();
}
if (descriptor.getType().equals(Object.class) && path.isLastNode(index)) {
extend = BLANK;
}
wrapper.setPropertyValue(extensionName, extend);
}
private String getActualPropertyName(BeanWrapper target, String prefix, String name) {
String propertyName = resolvePropertyName(target, prefix, name);
if (propertyName == null) {
propertyName = resolveNestedPropertyName(target, prefix, name);
}
return (propertyName != null ? propertyName : name);
}
private String resolveNestedPropertyName(BeanWrapper target, String prefix, String name) {
StringBuilder candidate = new StringBuilder();
for (String field : name.split("[_\\-\\.]")) {
candidate.append(candidate.length() > 0 ? "." : "");
candidate.append(field);
String nested = resolvePropertyName(target, prefix, candidate.toString());
if (nested != null) {
Class<?> type = target.getPropertyType(nested);
if ((type != null) && Map.class.isAssignableFrom(type)) {
// Special case for map property (gh-3836).
return nested + "[" + name.substring(candidate.length() + 1) + "]";
}
String propertyName = resolvePropertyName(target,
joinString(prefix, nested),
name.substring(candidate.length() + 1));
if (propertyName != null) {
return joinString(nested, propertyName);
}
}
}
return null;
}
private String resolvePropertyName(BeanWrapper target, String prefix, String name) {
Iterable<String> names = getNameAndAliases(name);
for (String nameOrAlias : names) {
for (String candidate : new RelaxedNames(nameOrAlias)) {
try {
if (target.getPropertyType(joinString(prefix, candidate)) != null) {
return candidate;
}
} catch (InvalidPropertyException ex) {
// swallow and continue
}
}
}
return null;
}
private String joinString(String prefix, String name) {
return (StringUtils.hasLength(prefix) ? prefix + "." + name : name);
}
private Iterable<String> getNameAndAliases(String name) {
List<String> aliases = this.nameAliases.get(name);
if (aliases == null) {
return Collections.singleton(name);
}
List<String> nameAndAliases = new ArrayList<String>(aliases.size() + 1);
nameAndAliases.add(name);
nameAndAliases.addAll(aliases);
return nameAndAliases;
}
private static Object wrapTarget(Object target) {
if (target instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) target;
target = new RelaxedDataBinder.MapHolder(map);
}
return target;
}
@Override
public void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor) {
if (propertyEditor == null || !EXCLUDED_EDITORS.contains(propertyEditor.getClass())) {
super.registerCustomEditor(requiredType, propertyEditor);
}
}
@Override
public void registerCustomEditor(Class<?> requiredType, String field, PropertyEditor propertyEditor) {
if (propertyEditor == null || !EXCLUDED_EDITORS.contains(propertyEditor.getClass())) {
super.registerCustomEditor(requiredType, field, propertyEditor);
}
}
/**
* Holder to allow Map targets to be bound.
*/
static class MapHolder {
private Map<String, Object> map;
MapHolder(Map<String, Object> map){
this.map = map;
}
public void setMap(Map<String, Object> map) {
this.map = map;
}
public Map<String, Object> getMap() {
return this.map;
}
}
/**
* A path though properties of a bean.
*/
private static class BeanPath {
private List<PathNode> nodes;
BeanPath(String path){
this.nodes = splitPath(path);
}
public List<String> prefixes() {
List<String> prefixes = new ArrayList<String>();
for (int index = 1; index < this.nodes.size(); index++) {
prefixes.add(prefix(index));
}
return prefixes;
}
public boolean isLastNode(int index) {
return index >= this.nodes.size() - 1;
}
private List<PathNode> splitPath(String path) {
List<PathNode> nodes = new ArrayList<PathNode>();
String current = extractIndexedPaths(path, nodes);
for (String name : StringUtils.delimitedListToStringArray(current, ".")) {
if (StringUtils.hasText(name)) {
nodes.add(new RelaxedDataBinder.BeanPath.PropertyNode(name));
}
}
return nodes;
}
private String extractIndexedPaths(String path, List<PathNode> nodes) {
int startRef = path.indexOf("[");
String current = path;
while (startRef >= 0) {
if (startRef > 0) {
nodes.addAll(splitPath(current.substring(0, startRef)));
}
int endRef = current.indexOf("]", startRef);
if (endRef > 0) {
String sub = current.substring(startRef + 1, endRef);
if (sub.matches("[0-9]+")) {
nodes.add(new RelaxedDataBinder.BeanPath.ArrayIndexNode(sub));
} else {
nodes.add(new RelaxedDataBinder.BeanPath.MapIndexNode(sub));
}
}
current = current.substring(endRef + 1);
startRef = current.indexOf("[");
}
return current;
}
public void collapseKeys(int index) {
List<PathNode> revised = new ArrayList<PathNode>();
for (int i = 0; i < index; i++) {
revised.add(this.nodes.get(i));
}
StringBuilder builder = new StringBuilder();
for (int i = index; i < this.nodes.size(); i++) {
if (i > index) {
builder.append(".");
}
builder.append(this.nodes.get(i).name);
}
revised.add(new RelaxedDataBinder.BeanPath.PropertyNode(builder.toString()));
this.nodes = revised;
}
public void mapIndex(int index) {
RelaxedDataBinder.BeanPath.PathNode node = this.nodes.get(index);
if (node instanceof RelaxedDataBinder.BeanPath.PropertyNode) {
node = ((RelaxedDataBinder.BeanPath.PropertyNode) node).mapIndex();
}
this.nodes.set(index, node);
}
public String prefix(int index) {
return range(0, index);
}
public void rename(int index, String name) {
this.nodes.get(index).name = name;
}
public String name(int index) {
if (index < this.nodes.size()) {
return this.nodes.get(index).name;
}
return null;
}
private String range(int start, int end) {
StringBuilder builder = new StringBuilder();
for (int i = start; i < end; i++) {
RelaxedDataBinder.BeanPath.PathNode node = this.nodes.get(i);
builder.append(node);
}
if (builder.toString().startsWith(("."))) {
builder.replace(0, 1, "");
}
return builder.toString();
}
public boolean isArrayIndex(int index) {
return this.nodes.get(index) instanceof RelaxedDataBinder.BeanPath.ArrayIndexNode;
}
public boolean isProperty(int index) {
return this.nodes.get(index) instanceof RelaxedDataBinder.BeanPath.PropertyNode;
}
@Override
public String toString() {
return prefix(this.nodes.size());
}
private static class PathNode {
protected String name;
PathNode(String name){
this.name = name;
}
}
private static class ArrayIndexNode extends RelaxedDataBinder.BeanPath.PathNode {
ArrayIndexNode(String name){
super(name);
}
@Override
public String toString() {
return "[" + this.name + "]";
}
}
private static class MapIndexNode extends RelaxedDataBinder.BeanPath.PathNode {
MapIndexNode(String name){
super(name);
}
@Override
public String toString() {
return "[" + this.name + "]";
}
}
private static class PropertyNode extends RelaxedDataBinder.BeanPath.PathNode {
PropertyNode(String name){
super(name);
}
public RelaxedDataBinder.BeanPath.MapIndexNode mapIndex() {
return new RelaxedDataBinder.BeanPath.MapIndexNode(this.name);
}
@Override
public String toString() {
return "." + this.name;
}
}
}
/**
* Extended version of {@link BeanPropertyBindingResult} to support relaxed
* binding.
*/
private static class RelaxedBeanPropertyBindingResult extends BeanPropertyBindingResult {
private RelaxedConversionService conversionService;
RelaxedBeanPropertyBindingResult(Object target, String objectName, boolean autoGrowNestedPaths,
int autoGrowCollectionLimit, ConversionService conversionService){
super(target, objectName, autoGrowNestedPaths, autoGrowCollectionLimit);
this.conversionService = new RelaxedConversionService(conversionService);
}
@Override
protected BeanWrapper createBeanWrapper() {
BeanWrapper beanWrapper = new RelaxedDataBinder.RelaxedBeanWrapper(getTarget());
beanWrapper.setConversionService(this.conversionService);
beanWrapper.registerCustomEditor(InetAddress.class, new InetAddressEditor());
return beanWrapper;
}
}
/**
* Extended version of {@link BeanWrapperImpl} to support relaxed binding.
*/
private static class RelaxedBeanWrapper extends BeanWrapperImpl {
private static final Set<String> BENIGN_PROPERTY_SOURCE_NAMES;
static {
Set<String> names = new HashSet<String>();
names.add(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME);
names.add(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME);
BENIGN_PROPERTY_SOURCE_NAMES = Collections.unmodifiableSet(names);
}
RelaxedBeanWrapper(Object target){
super(target);
}
@Override
public void setPropertyValue(PropertyValue pv) throws BeansException {
try {
super.setPropertyValue(pv);
} catch (NotWritablePropertyException ex) {
PropertyOrigin origin = OriginCapablePropertyValue.getOrigin(pv);
if (isBenign(origin)) {
logger.debug("Ignoring benign property binding failure", ex);
return;
}
if (origin == null) {
throw ex;
}
throw new RelaxedBindingNotWritablePropertyException(ex, origin);
}
}
private boolean isBenign(PropertyOrigin origin) {
String name = (origin != null ? origin.getSource().getName() : null);
return BENIGN_PROPERTY_SOURCE_NAMES.contains(name);
}
}
public static class RelaxedBindingNotWritablePropertyException extends NotWritablePropertyException {
private final String message;
private final PropertyOrigin propertyOrigin;
RelaxedBindingNotWritablePropertyException(NotWritablePropertyException ex, PropertyOrigin propertyOrigin){
super(ex.getBeanClass(), ex.getPropertyName());
this.propertyOrigin = propertyOrigin;
this.message = "Failed to bind '" + propertyOrigin.getName() + "' from '"
+ propertyOrigin.getSource().getName() + "' to '" + ex.getPropertyName() + "' property on '"
+ ex.getBeanClass().getName() + "'";
}
@Override
public String getMessage() {
return this.message;
}
public PropertyOrigin getPropertyOrigin() {
return this.propertyOrigin;
}
}
}
@@ -1,241 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.bind;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.util.StringUtils;
/**
* Generates relaxed name variations from a given source.
*
* @author Phillip Webb
* @author Dave Syer
* @see RelaxedDataBinder
*/
public final class RelaxedNames implements Iterable<String> {
private static final Pattern CAMEL_CASE_PATTERN = Pattern.compile("([^A-Z-])([A-Z])");
private static final Pattern SEPARATED_TO_CAMEL_CASE_PATTERN = Pattern.compile("[_\\-.]");
private final String name;
private final Set<String> values = new LinkedHashSet<String>();
/**
* Create a new {@link RelaxedNames} instance.
*
* @param name the source name. For the maximum number of variations specify the
* name using dashed notation (e.g. {@literal my-property-name}
*/
public RelaxedNames(String name){
this.name = (name != null ? name : "");
initialize(RelaxedNames.this.name, this.values);
}
@Override
public Iterator<String> iterator() {
return this.values.iterator();
}
private void initialize(String name, Set<String> values) {
if (values.contains(name)) {
return;
}
for (RelaxedNames.Variation variation : RelaxedNames.Variation.values()) {
for (RelaxedNames.Manipulation manipulation : RelaxedNames.Manipulation.values()) {
String result = name;
result = manipulation.apply(result);
result = variation.apply(result);
values.add(result);
initialize(result, values);
}
}
}
/**
* Name variations.
*/
enum Variation {
NONE {
@Override
public String apply(String value) {
return value;
}
},
LOWERCASE {
@Override
public String apply(String value) {
return (value.isEmpty() ? value : value.toLowerCase(Locale.ENGLISH));
}
},
UPPERCASE {
@Override
public String apply(String value) {
return (value.isEmpty() ? value : value.toUpperCase(Locale.ENGLISH));
}
};
public abstract String apply(String value);
}
/**
* Name manipulations.
*/
enum Manipulation {
NONE {
@Override
public String apply(String value) {
return value;
}
},
HYPHEN_TO_UNDERSCORE {
@Override
public String apply(String value) {
return (value.indexOf('-') != -1 ? value.replace('-', '_') : value);
}
},
UNDERSCORE_TO_PERIOD {
@Override
public String apply(String value) {
return (value.indexOf('_') != -1 ? value.replace('_', '.') : value);
}
},
PERIOD_TO_UNDERSCORE {
@Override
public String apply(String value) {
return (value.indexOf('.') != -1 ? value.replace('.', '_') : value);
}
},
CAMELCASE_TO_UNDERSCORE {
@Override
public String apply(String value) {
if (value.isEmpty()) {
return value;
}
Matcher matcher = CAMEL_CASE_PATTERN.matcher(value);
if (!matcher.find()) {
return value;
}
matcher = matcher.reset();
StringBuffer result = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(result,
matcher.group(1) + '_' + StringUtils.uncapitalize(matcher.group(2)));
}
matcher.appendTail(result);
return result.toString();
}
},
CAMELCASE_TO_HYPHEN {
@Override
public String apply(String value) {
if (value.isEmpty()) {
return value;
}
Matcher matcher = CAMEL_CASE_PATTERN.matcher(value);
if (!matcher.find()) {
return value;
}
matcher = matcher.reset();
StringBuffer result = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(result,
matcher.group(1) + '-' + StringUtils.uncapitalize(matcher.group(2)));
}
matcher.appendTail(result);
return result.toString();
}
},
SEPARATED_TO_CAMELCASE {
@Override
public String apply(String value) {
return separatedToCamelCase(value, false);
}
},
CASE_INSENSITIVE_SEPARATED_TO_CAMELCASE {
@Override
public String apply(String value) {
return separatedToCamelCase(value, true);
}
};
private static final char[] SUFFIXES = new char[] { '_', '-', '.' };
public abstract String apply(String value);
private static String separatedToCamelCase(String value, boolean caseInsensitive) {
if (value.isEmpty()) {
return value;
}
StringBuilder builder = new StringBuilder();
for (String field : SEPARATED_TO_CAMEL_CASE_PATTERN.split(value)) {
field = (caseInsensitive ? field.toLowerCase(Locale.ENGLISH) : field);
builder.append(builder.length() != 0 ? StringUtils.capitalize(field) : field);
}
char lastChar = value.charAt(value.length() - 1);
for (char suffix : SUFFIXES) {
if (lastChar == suffix) {
builder.append(suffix);
break;
}
}
return builder.toString();
}
}
/**
* Return a {@link RelaxedNames} for the given source camelCase source name.
*
* @param name the source name in camelCase
* @return the relaxed names
*/
public static RelaxedNames forCamelCase(String name) {
StringBuilder result = new StringBuilder();
for (char c : name.toCharArray()) {
result.append(Character.isUpperCase(c) && result.length() > 0
&& result.charAt(result.length() - 1) != '-' ? "-" + Character.toLowerCase(c) : c);
}
return new RelaxedNames(result.toString());
}
}
@@ -1,17 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.bind;
import org.springframework.core.convert.converter.Converter;
/**
* Converts a String to a Char Array.
*
* @author Phillip Webb
*/
class StringToCharArrayConverter implements Converter<String, char[]> {
@Override
public char[] convert(String source) {
return source.toCharArray();
}
}
@@ -1,203 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.common;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import org.springframework.util.Assert;
import org.springframework.util.ResourceUtils;
/**
* Convenience base class for {@link Resource} implementations, pre-implementing
* typical behavior.
* <p>
* The "exists" method will check whether a File or InputStream can be opened;
* "isOpen" will always return false; "getURL" and "getFile" throw an exception;
* and "toString" will return the description.
*
* @author Juergen Hoeller
* @since 28.12.2003
*/
public abstract class AbstractResource implements Resource {
/**
* This implementation checks whether a File can be opened, falling back to
* whether an InputStream can be opened. This will cover both directories and
* content resources.
*/
@Override
public boolean exists() {
// Try file existence: can we find the file in the file system?
try {
return getFile().exists();
} catch (IOException ex) {
// Fall back to stream existence: can we open the stream?
try {
InputStream is = getInputStream();
is.close();
return true;
} catch (Throwable isEx) {
return false;
}
}
}
/**
* This implementation always returns {@code true}.
*/
@Override
public boolean isReadable() {
return true;
}
/**
* This implementation always returns {@code false}.
*/
@Override
public boolean isOpen() {
return false;
}
/**
* This implementation throws a FileNotFoundException, assuming that the
* resource cannot be resolved to a URL.
*/
@Override
public URL getURL() throws IOException {
throw new FileNotFoundException(getDescription() + " cannot be resolved to URL");
}
/**
* This implementation builds a URI based on the URL returned by
* {@link #getURL()}.
*/
@Override
public URI getURI() throws IOException {
URL url = getURL();
try {
return ResourceUtils.toURI(url);
} catch (URISyntaxException ex) {
throw new RuntimeException("Invalid URI [" + url + "]", ex);
}
}
/**
* This implementation throws a FileNotFoundException, assuming that the
* resource cannot be resolved to an absolute file path.
*/
@Override
public File getFile() throws IOException {
throw new FileNotFoundException(getDescription() + " cannot be resolved to absolute file path");
}
/**
* This implementation reads the entire InputStream to calculate the content
* length. Subclasses will almost always be able to provide a more optimal
* version of this, e.g. checking a File length.
*
* @see #getInputStream()
*/
@Override
public long contentLength() throws IOException {
InputStream is = getInputStream();
Assert.state(is != null, "Resource InputStream must not be null");
try {
long size = 0;
byte[] buf = new byte[255];
int read;
while ((read = is.read(buf)) != -1) {
size += read;
}
return size;
} finally {
try {
is.close();
} catch (IOException ex) {
}
}
}
/**
* This implementation checks the timestamp of the underlying File, if
* available.
*
* @see #getFileForLastModifiedCheck()
*/
@Override
public long lastModified() throws IOException {
long lastModified = getFileForLastModifiedCheck().lastModified();
if (lastModified == 0L) {
throw new FileNotFoundException(
getDescription() + " cannot be resolved in the file system for resolving its last-modified timestamp");
}
return lastModified;
}
/**
* Determine the File to use for timestamp checking.
* <p>
* The default implementation delegates to {@link #getFile()}.
*
* @return the File to use for timestamp checking (never {@code null})
* @throws FileNotFoundException if the resource cannot be resolved as an
* absolute file path, i.e. is not available in a file system
* @throws IOException in case of general resolution/reading failures
*/
protected File getFileForLastModifiedCheck() throws IOException {
return getFile();
}
/**
* This implementation throws a FileNotFoundException, assuming that relative
* resources cannot be created for this resource.
*/
@Override
public org.springframework.core.io.Resource createRelative(String relativePath) throws IOException {
throw new FileNotFoundException("Cannot create a relative resource for " + getDescription());
}
/**
* This implementation always returns {@code null}, assuming that this resource
* type does not have a filename.
*/
@Override
public String getFilename() {
return null;
}
/**
* This implementation returns the description of this resource.
*
* @see #getDescription()
*/
@Override
public String toString() {
return getDescription();
}
/**
* This implementation compares description strings.
*
* @see #getDescription()
*/
@Override
public boolean equals(Object obj) {
return (obj == this
|| (obj instanceof org.springframework.core.io.Resource
&& ((org.springframework.core.io.Resource) obj).getDescription().equals(getDescription())));
}
/**
* This implementation returns the description's hash code.
*
* @see #getDescription()
*/
@Override
public int hashCode() {
return getDescription().hashCode();
}
}
@@ -1,117 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.common;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
/**
* {@link Resource} implementation for a given byte array.
* <p>
* Creates a {@link ByteArrayInputStream} for the given byte array.
* <p>
* Useful for loading content from any given byte array, without having to
* resort to a single-use {@link InputStreamResource}. Particularly useful for
* creating mail attachments from local content, where JavaMail needs to be able
* to read the stream multiple times.
*
* @author Juergen Hoeller
* @author Sam Brannen
* @see ByteArrayInputStream
* @see InputStreamResource
* @since 1.2.3
*/
public class ByteArrayResource extends AbstractResource {
private final byte[] byteArray;
private final String description;
/**
* Create a new {@code ByteArrayResource}.
*
* @param byteArray the byte array to wrap
*/
public ByteArrayResource(byte[] byteArray){
this(byteArray, "resource loaded from byte array");
}
/**
* Create a new {@code ByteArrayResource} with a description.
*
* @param byteArray the byte array to wrap
* @param description where the byte array comes from
*/
public ByteArrayResource(byte[] byteArray, String description){
Assert.notNull(byteArray, "Byte array must not be null");
this.byteArray = byteArray;
this.description = (description != null ? description : "");
}
/**
* Return the underlying byte array.
*/
public final byte[] getByteArray() {
return this.byteArray;
}
/**
* This implementation always returns {@code true}.
*/
@Override
public boolean exists() {
return true;
}
/**
* This implementation returns the length of the underlying byte array.
*/
@Override
public long contentLength() {
return this.byteArray.length;
}
/**
* This implementation returns a ByteArrayInputStream for the underlying byte
* array.
*
* @see ByteArrayInputStream
*/
@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(this.byteArray);
}
/**
* This implementation returns a description that includes the passed-in
* {@code description}, if any.
*/
@Override
public String getDescription() {
return "Byte array resource [" + this.description + "]";
}
/**
* This implementation compares the underlying byte array.
*
* @see Arrays#equals(byte[], byte[])
*/
@Override
public boolean equals(Object obj) {
return (obj == this || (obj instanceof org.springframework.core.io.ByteArrayResource
&& Arrays.equals(((ByteArrayResource) obj).byteArray, this.byteArray)));
}
/**
* This implementation returns the hash code based on the underlying byte array.
*/
@Override
public int hashCode() {
return (byte[].class.hashCode() * 29 * this.byteArray.length);
}
}
@@ -1,107 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.common;
import java.util.*;
import org.springframework.util.StringUtils;
/**
* Composite {@link PropertySource} implementation that iterates over a set of
* {@link PropertySource} instances. Necessary in cases where multiple property
* sources share the same name, e.g. when multiple values are supplied to
* {@code @PropertySource}.
* <p>
* As of Spring 4.1.2, this class extends {@link EnumerablePropertySource}
* instead of plain {@link PropertySource}, exposing {@link #getPropertyNames()}
* based on the accumulated property names from all contained sources (as far as
* possible).
*
* @author Chris Beams
* @author Juergen Hoeller
* @author Phillip Webb
* @since 3.1.1
*/
public class CompositePropertySource extends EnumerablePropertySource<Object> {
private final Set<PropertySource<?>> propertySources = new LinkedHashSet<PropertySource<?>>();
/**
* Create a new {@code CompositePropertySource}.
*
* @param name the name of the property source
*/
public CompositePropertySource(String name){
super(name);
}
@Override
public Object getProperty(String name) {
for (PropertySource<?> propertySource : this.propertySources) {
Object candidate = propertySource.getProperty(name);
if (candidate != null) {
return candidate;
}
}
return null;
}
@Override
public boolean containsProperty(String name) {
for (PropertySource<?> propertySource : this.propertySources) {
if (propertySource.containsProperty(name)) {
return true;
}
}
return false;
}
@Override
public String[] getPropertyNames() {
Set<String> names = new LinkedHashSet<String>();
for (PropertySource<?> propertySource : this.propertySources) {
if (!(propertySource instanceof EnumerablePropertySource)) {
throw new IllegalStateException(
"Failed to enumerate property names due to non-enumerable property source: " + propertySource);
}
names.addAll(Arrays.asList(((EnumerablePropertySource<?>) propertySource).getPropertyNames()));
}
return StringUtils.toStringArray(names);
}
/**
* Add the given {@link PropertySource} to the end of the chain.
*
* @param propertySource the PropertySource to add
*/
public void addPropertySource(PropertySource<?> propertySource) {
this.propertySources.add(propertySource);
}
/**
* Add the given {@link PropertySource} to the start of the chain.
*
* @param propertySource the PropertySource to add
* @since 4.1
*/
public void addFirstPropertySource(PropertySource<?> propertySource) {
List<PropertySource<?>> existing = new ArrayList<PropertySource<?>>(this.propertySources);
this.propertySources.clear();
this.propertySources.add(propertySource);
this.propertySources.addAll(existing);
}
/**
* Return all property sources that this composite source holds.
*
* @since 4.1.1
*/
public Collection<PropertySource<?>> getPropertySources() {
return this.propertySources;
}
@Override
public String toString() {
return String
.format("%s [name='%s', propertySources=%s]", getClass().getSimpleName(), this.name, this.propertySources);
}
}
@@ -1,58 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.common;
import org.springframework.util.ObjectUtils;
/**
* A {@link PropertySource} implementation capable of interrogating its
* underlying source object to enumerate all possible property name/value pairs.
* Exposes the {@link #getPropertyNames()} method to allow callers to introspect
* available properties without having to access the underlying source object.
* This also facilitates a more efficient implementation of
* {@link #containsProperty(String)}, in that it can call
* {@link #getPropertyNames()} and iterate through the returned array rather
* than attempting a call to {@link #getProperty(String)} which may be more
* expensive. Implementations may consider caching the result of
* {@link #getPropertyNames()} to fully exploit this performance opportunity.
* <p>
* Most framework-provided {@code PropertySource} implementations are
* enumerable; a counter-example would be {@code JndiPropertySource} where, due
* to the nature of JNDI it is not possible to determine all possible property
* names at any given time; rather it is only possible to try to access a
* property (via {@link #getProperty(String)}) in order to evaluate whether it
* is present or not.
*
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.1
*/
public abstract class EnumerablePropertySource<T> extends PropertySource<T> {
public EnumerablePropertySource(String name, T source){
super(name, source);
}
protected EnumerablePropertySource(String name){
super(name);
}
/**
* Return whether this {@code PropertySource} contains a property with the given
* name.
* <p>
* This implementation checks for the presence of the given name within the
* {@link #getPropertyNames()} array.
*
* @param name the name of the property to find
*/
@Override
public boolean containsProperty(String name) {
return ObjectUtils.containsElement(getPropertyNames(), name);
}
/**
* Return the names of all properties contained by the {@linkplain #getSource()
* source} object (never {@code null}).
*/
public abstract String[] getPropertyNames();
}
@@ -1,38 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.common;
import java.util.Map;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.util.StringUtils;
/**
* {@link PropertySource} that reads keys and values from a {@code Map} object.
*
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.1
* @see PropertiesPropertySource
*/
public class MapPropertySource extends EnumerablePropertySource<Map<String, Object>> {
public MapPropertySource(String name, Map<String, Object> source){
super(name, source);
}
@Override
public Object getProperty(String name) {
return this.source.get(name);
}
@Override
public boolean containsProperty(String name) {
return this.source.containsKey(name);
}
@Override
public String[] getPropertyNames() {
return StringUtils.toStringArray(this.source.keySet());
}
}
@@ -1,221 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.common;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.env.PropertyResolver;
import org.springframework.core.env.PropertySourcesPropertyResolver;
/**
* Default implementation of the {@link PropertySources} interface. Allows
* manipulation of contained property sources and provides a constructor for
* copying an existing {@code PropertySources} instance.
* <p>
* Where <em>precedence</em> is mentioned in methods such as {@link #addFirst}
* and {@link #addLast}, this is with regard to the order in which property
* sources will be searched when resolving a given property with a
* {@link PropertyResolver}.
*
* @author Chris Beams
* @author Juergen Hoeller
* @see PropertySourcesPropertyResolver
* @since 3.1
*/
public class MutablePropertySources implements PropertySources {
private final Log logger;
private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<PropertySource<?>>();
/**
* Create a new {@link MutablePropertySources}
* object.
*/
public MutablePropertySources(){
this.logger = LogFactory.getLog(getClass());
}
/**
* Create a new {@code MutablePropertySources} from the given propertySources
* object, preserving the original order of contained {@code PropertySource}
* objects.
*/
public MutablePropertySources(PropertySources propertySources){
this();
for (PropertySource<?> propertySource : propertySources) {
addLast(propertySource);
}
}
/**
* Create a new {@link MutablePropertySources}
* object and inherit the given logger, usually from an enclosing
* {@link Environment}.
*/
MutablePropertySources(Log logger){
this.logger = logger;
}
@Override
public boolean contains(String name) {
return this.propertySourceList.contains(PropertySource.named(name));
}
@Override
public PropertySource<?> get(String name) {
int index = this.propertySourceList.indexOf(PropertySource.named(name));
return (index != -1 ? this.propertySourceList.get(index) : null);
}
@Override
public Iterator<PropertySource<?>> iterator() {
return this.propertySourceList.iterator();
}
/**
* Add the given property source object with highest precedence.
*/
public void addFirst(PropertySource<?> propertySource) {
if (logger.isDebugEnabled()) {
logger.debug("Adding PropertySource '" + propertySource.getName() + "' with highest search precedence");
}
removeIfPresent(propertySource);
this.propertySourceList.add(0, propertySource);
}
/**
* Add the given property source object with lowest precedence.
*/
public void addLast(PropertySource<?> propertySource) {
if (logger.isDebugEnabled()) {
logger.debug("Adding PropertySource '" + propertySource.getName() + "' with lowest search precedence");
}
removeIfPresent(propertySource);
this.propertySourceList.add(propertySource);
}
/**
* Add the given property source object with precedence immediately higher than
* the named relative property source.
*/
public void addBefore(String relativePropertySourceName, PropertySource<?> propertySource) {
if (logger.isDebugEnabled()) {
logger.debug("Adding PropertySource '" + propertySource.getName()
+ "' with search precedence immediately higher than '" + relativePropertySourceName + "'");
}
assertLegalRelativeAddition(relativePropertySourceName, propertySource);
removeIfPresent(propertySource);
int index = assertPresentAndGetIndex(relativePropertySourceName);
addAtIndex(index, propertySource);
}
/**
* Add the given property source object with precedence immediately lower than
* the named relative property source.
*/
public void addAfter(String relativePropertySourceName, PropertySource<?> propertySource) {
if (logger.isDebugEnabled()) {
logger.debug("Adding PropertySource '" + propertySource.getName()
+ "' with search precedence immediately lower than '" + relativePropertySourceName + "'");
}
assertLegalRelativeAddition(relativePropertySourceName, propertySource);
removeIfPresent(propertySource);
int index = assertPresentAndGetIndex(relativePropertySourceName);
addAtIndex(index + 1, propertySource);
}
/**
* Return the precedence of the given property source, {@code -1} if not found.
*/
public int precedenceOf(PropertySource<?> propertySource) {
return this.propertySourceList.indexOf(propertySource);
}
/**
* Remove and return the property source with the given name, {@code null} if
* not found.
*
* @param name the name of the property source to find and remove
*/
public PropertySource<?> remove(String name) {
if (logger.isDebugEnabled()) {
logger.debug("Removing PropertySource '" + name + "'");
}
int index = this.propertySourceList.indexOf(PropertySource.named(name));
return (index != -1 ? this.propertySourceList.remove(index) : null);
}
/**
* Replace the property source with the given name with the given property
* source object.
*
* @param name the name of the property source to find and replace
* @param propertySource the replacement property source
* @throws IllegalArgumentException if no property source with the given name is
* present
* @see #contains
*/
public void replace(String name, PropertySource<?> propertySource) {
if (logger.isDebugEnabled()) {
logger.debug("Replacing PropertySource '" + name + "' with '" + propertySource.getName() + "'");
}
int index = assertPresentAndGetIndex(name);
this.propertySourceList.set(index, propertySource);
}
/**
* Return the number of {@link PropertySource} objects contained.
*/
public int size() {
return this.propertySourceList.size();
}
@Override
public String toString() {
return this.propertySourceList.toString();
}
/**
* Ensure that the given property source is not being added relative to itself.
*/
protected void assertLegalRelativeAddition(String relativePropertySourceName, PropertySource<?> propertySource) {
String newPropertySourceName = propertySource.getName();
if (relativePropertySourceName.equals(newPropertySourceName)) {
throw new IllegalArgumentException(
"PropertySource named '" + newPropertySourceName + "' cannot be added relative to itself");
}
}
/**
* Remove the given property source if it is present.
*/
protected void removeIfPresent(PropertySource<?> propertySource) {
this.propertySourceList.remove(propertySource);
}
/**
* Add the given property source at a particular index in the list.
*/
private void addAtIndex(int index, PropertySource<?> propertySource) {
removeIfPresent(propertySource);
this.propertySourceList.add(index, propertySource);
}
/**
* Assert that the named property source is present and return its index.
*
* @param name {@linkplain PropertySource#getName() name of the property source}
* to find
* @throws IllegalArgumentException if the named property source is not present
*/
private int assertPresentAndGetIndex(String name) {
int index = this.propertySourceList.indexOf(PropertySource.named(name));
if (index == -1) {
throw new IllegalArgumentException("PropertySource named '" + name + "' does not exist");
}
return index;
}
}
@@ -1,34 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.common;
import java.util.Map;
import java.util.Properties;
import org.springframework.core.env.PropertySource;
/**
* {@link PropertySource} implementation that extracts properties from a
* {@link Properties} object.
* <p>
* Note that because a {@code Properties} object is technically an
* {@code <Object, Object>} {@link java.util.Hashtable Hashtable}, one may
* contain non-{@code String} keys or values. This implementation, however is
* restricted to accessing only {@code String}-based keys and values, in the
* same fashion as {@link Properties#getProperty} and
* {@link Properties#setProperty}.
*
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.1
*/
public class PropertiesPropertySource extends MapPropertySource {
@SuppressWarnings({ "unchecked", "rawtypes" })
public PropertiesPropertySource(String name, Properties source){
super(name, (Map) source);
}
protected PropertiesPropertySource(String name, Map<String, Object> source){
super(name, source);
}
}
@@ -1,239 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.common;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* Abstract base class representing a source of name/value property pairs. The
* underlying {@linkplain #getSource() source object} may be of any type
* {@code T} that encapsulates properties. Examples include
* {@link java.util.Properties} objects, {@link java.util.Map} objects,
* {@code ServletContext} and {@code ServletConfig} objects (for access to init
* parameters). Explore the {@code PropertySource} type hierarchy to see
* provided implementations.
* <p>
* {@code PropertySource} objects are not typically used in isolation, but
* rather through a {@link PropertySources} object, which aggregates property
* sources and in conjunction with a {@link PropertyResolver} implementation
* that can perform precedence-based searches across the set of
* {@code PropertySources}.
* <p>
* {@code PropertySource} identity is determined not based on the content of
* encapsulated properties, but rather based on the {@link #getName() name} of
* the {@code PropertySource} alone. This is useful for manipulating
* {@code PropertySource} objects when in collection contexts. See operations in
* {@link MutablePropertySources} as well as the {@link #named(String)} and
* {@link #toString()} methods for details.
* <p>
* Note that when working
* with @{@link org.springframework.context.annotation.Configuration
* Configuration} classes that the @{@link PropertySource PropertySource}
* annotation provides a convenient and declarative way of adding property
* sources to the enclosing {@code Environment}.
*
* @author Chris Beams
* @since 3.1
* @see PropertySources
* @see MutablePropertySources
* @see PropertySource
*/
public abstract class PropertySource<T> {
protected final Log logger = LogFactory.getLog(getClass());
protected final String name;
protected final T source;
/**
* Create a new {@code PropertySource} with the given name and source object.
*/
public PropertySource(String name, T source){
Assert.hasText(name, "Property source name must contain at least one character");
Assert.notNull(source, "Property source must not be null");
this.name = name;
this.source = source;
}
/**
* Create a new {@code PropertySource} with the given name and with a new
* {@code Object} instance as the underlying source.
* <p>
* Often useful in testing scenarios when creating anonymous implementations
* that never query an actual source but rather return hard-coded values.
*/
@SuppressWarnings("unchecked")
public PropertySource(String name){
this(name, (T) new Object());
}
/**
* Return the name of this {@code PropertySource}
*/
public String getName() {
return this.name;
}
/**
* Return the underlying source object for this {@code PropertySource}.
*/
public T getSource() {
return this.source;
}
/**
* Return whether this {@code PropertySource} contains the given name.
* <p>
* This implementation simply checks for a {@code null} return value from
* {@link #getProperty(String)}. Subclasses may wish to implement a more
* efficient algorithm if possible.
*
* @param name the property name to find
*/
public boolean containsProperty(String name) {
return (getProperty(name) != null);
}
/**
* Return the value associated with the given name, or {@code null} if not
* found.
*
* @param name the property to find
*/
public abstract Object getProperty(String name);
/**
* This {@code PropertySource} object is equal to the given object if:
* <ul>
* <li>they are the same instance
* <li>the {@code name} properties for both objects are equal
* </ul>
* <p>
* No properties other than {@code name} are evaluated.
*/
@Override
public boolean equals(Object obj) {
return (this == obj || (obj instanceof PropertySource
&& ObjectUtils.nullSafeEquals(this.name, ((PropertySource<?>) obj).name)));
}
/**
* Return a hash code derived from the {@code name} property of this
* {@code PropertySource} object.
*/
@Override
public int hashCode() {
return ObjectUtils.nullSafeHashCode(this.name);
}
/**
* Produce concise output (type and name) if the current log level does not
* include debug. If debug is enabled, produce verbose output including the hash
* code of the PropertySource instance and every name/value property pair.
* <p>
* This variable verbosity is useful as a property source such as system
* properties or environment variables may contain an arbitrary number of
* property pairs, potentially leading to difficult to read exception and log
* messages.
*
* @see Log#isDebugEnabled()
*/
@Override
public String toString() {
if (logger.isDebugEnabled()) {
return getClass().getSimpleName() + "@" + System.identityHashCode(this) + " {name='" + this.name
+ "', properties=" + this.source + "}";
} else {
return getClass().getSimpleName() + " {name='" + this.name + "'}";
}
}
/**
* Return a {@code PropertySource} implementation intended for collection
* comparison purposes only.
* <p>
* Primarily for internal use, but given a collection of {@code PropertySource}
* objects, may be used as follows:
*
* <pre class="code">
*
* {
* &#64;code
* List<PropertySource<?>> sources = new ArrayList<PropertySource<?>>();
* sources.add(new MapPropertySource("sourceA", mapA));
* sources.add(new MapPropertySource("sourceB", mapB));
* assert sources.contains(PropertySource.named("sourceA"));
* assert sources.contains(PropertySource.named("sourceB"));
* assert !sources.contains(PropertySource.named("sourceC"));
* }
* </pre>
*
* The returned {@code PropertySource} will throw
* {@code UnsupportedOperationException} if any methods other than
* {@code equals(Object)}, {@code hashCode()}, and {@code toString()} are
* called.
*
* @param name the name of the comparison {@code PropertySource} to be created
* and returned.
*/
public static PropertySource<?> named(String name) {
return new ComparisonPropertySource(name);
}
/**
* {@code PropertySource} to be used as a placeholder in cases where an actual
* property source cannot be eagerly initialized at application context creation
* time. For example, a {@code ServletContext}-based property source must wait
* until the {@code ServletContext} object is available to its enclosing
* {@code ApplicationContext}. In such cases, a stub should be used to hold the
* intended default position/order of the property source, then be replaced
* during context refresh.
*
* @see org.springframework.web.context.support.StandardServletEnvironment
* @see org.springframework.web.context.support.ServletContextPropertySource
*/
public static class StubPropertySource extends PropertySource<Object> {
public StubPropertySource(String name){
super(name, new Object());
}
/**
* Always returns {@code null}.
*/
@Override
public String getProperty(String name) {
return null;
}
}
/**
* @see PropertySource#named(String)
*/
static class ComparisonPropertySource extends StubPropertySource {
private static final String USAGE_ERROR = "ComparisonPropertySource instances are for use with collection comparison only";
public ComparisonPropertySource(String name){
super(name);
}
@Override
public Object getSource() {
throw new UnsupportedOperationException(USAGE_ERROR);
}
@Override
public boolean containsProperty(String name) {
throw new UnsupportedOperationException(USAGE_ERROR);
}
@Override
public String getProperty(String name) {
throw new UnsupportedOperationException(USAGE_ERROR);
}
}
}
@@ -1,35 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.common;
import java.io.IOException;
import org.springframework.core.io.support.SpringFactoriesLoader;
/**
* Strategy interface located via {@link SpringFactoriesLoader} and used to load
* a {@link PropertySource}.
*
* @author Dave Syer
* @author Phillip Webb
*/
public interface PropertySourceLoader {
/**
* Returns the file extensions that the loader supports (excluding the '.').
*
* @return the file extensions
*/
String[] getFileExtensions();
/**
* Load the resource into a property source.
*
* @param name the name of the property source
* @param resource the resource to load
* @param profile the name of the profile to load or {@code null}. The profile
* can be used to load multi-document files (such as YAML). Simple property
* formats should {@code null} when asked to load a profile.
* @return a property source or {@code null}
* @throws IOException if the source cannot be loaded
*/
PropertySource<?> load(String name, Resource resource, String profile) throws IOException;
}
@@ -1,25 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.common;
/**
* Holder containing one or more {@link PropertySource} objects.
*
* @author Chris Beams
* @since 3.1
*/
public interface PropertySources extends Iterable<PropertySource<?>> {
/**
* Return whether a property source with the given name is contained.
*
* @param name the {@linkplain PropertySource#getName() name of the property source} to find
*/
boolean contains(String name);
/**
* Return the property source with the given name, {@code null} if not found.
*
* @param name the {@linkplain PropertySource#getName() name of the property source} to find
*/
PropertySource<?> get(String name);
}
@@ -1,57 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.common;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import org.springframework.core.io.*;
import org.springframework.core.io.ByteArrayResource;
/**
* Interface for a resource descriptor that abstracts from the actual type of
* underlying resource, such as a file or class path resource.
* <p>
* An InputStream can be opened for every resource if it exists in physical
* form, but a URL or File handle can just be returned for certain resources.
* The actual behavior is implementation-specific.
*
* @author Juergen Hoeller
* @since 28.12.2003
* @see #getInputStream()
* @see #getURL()
* @see #getURI()
* @see #getFile()
* @see WritableResource
* @see ContextResource
* @see UrlResource
* @see ClassPathResource
* @see FileSystemResource
* @see PathResource
* @see ByteArrayResource
* @see InputStreamResource
*/
public interface Resource extends InputStreamSource {
boolean exists();
boolean isReadable();
boolean isOpen();
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException;
long contentLength() throws IOException;
long lastModified() throws IOException;
org.springframework.core.io.Resource createRelative(String var1) throws IOException;
String getFilename();
String getDescription();
}
@@ -1,182 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.common;
import java.util.*;
import org.springframework.core.env.Environment;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
* {@link YamlProcessor.DocumentMatcher} backed by
* {@link Environment#getActiveProfiles()}. A YAML document may define a
* "spring.profiles" element as a comma-separated list of Spring profile names,
* optionally negated using the {@code !} character. If both negated and
* non-negated profiles are specified for a single document, at least one
* non-negated profile must match and no negated profiles may match.
*
* @author Dave Syer
* @author Matt Benson
* @author Phillip Webb
* @author Andy Wilkinson
*/
public class SpringProfileDocumentMatcher implements YamlProcessor.DocumentMatcher {
private String[] activeProfiles = new String[0];
public SpringProfileDocumentMatcher(){
}
public SpringProfileDocumentMatcher(String... profiles){
addActiveProfiles(profiles);
}
public void addActiveProfiles(String... profiles) {
LinkedHashSet<String> set = new LinkedHashSet<String>(Arrays.asList(this.activeProfiles));
Collections.addAll(set, profiles);
this.activeProfiles = set.toArray(new String[set.size()]);
}
@Override
public YamlProcessor.MatchStatus matches(Properties properties) {
List<String> profiles = extractSpringProfiles(properties);
ProfilesMatcher profilesMatcher = getProfilesMatcher();
Set<String> negative = extractProfiles(profiles, ProfileType.NEGATIVE);
Set<String> positive = extractProfiles(profiles, ProfileType.POSITIVE);
if (!CollectionUtils.isEmpty(negative)) {
if (profilesMatcher.matches(negative) == YamlProcessor.MatchStatus.FOUND) {
return YamlProcessor.MatchStatus.NOT_FOUND;
}
if (CollectionUtils.isEmpty(positive)) {
return YamlProcessor.MatchStatus.FOUND;
}
}
return profilesMatcher.matches(positive);
}
private List<String> extractSpringProfiles(Properties properties) {
SpringProperties springProperties = new SpringProperties();
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(new PropertiesPropertySource("profiles", properties));
// PropertyValues propertyValues = new PropertySourcesPropertyValues(
// propertySources);
// new RelaxedDataBinder(springProperties, "spring").bind(propertyValues);
// TODO
List<String> profiles = springProperties.getProfiles();
return profiles;
}
private ProfilesMatcher getProfilesMatcher() {
return (this.activeProfiles.length != 0 ? new ActiveProfilesMatcher(
new HashSet<String>(Arrays.asList(this.activeProfiles))) : new EmptyProfilesMatcher());
}
private Set<String> extractProfiles(List<String> profiles, ProfileType type) {
if (CollectionUtils.isEmpty(profiles)) {
return null;
}
Set<String> extractedProfiles = new HashSet<String>();
for (String candidate : profiles) {
ProfileType candidateType = ProfileType.POSITIVE;
if (candidate.startsWith("!")) {
candidateType = ProfileType.NEGATIVE;
}
if (candidateType == type) {
extractedProfiles.add(type != ProfileType.POSITIVE ? candidate.substring(1) : candidate);
}
}
return extractedProfiles;
}
/**
* Profile match types.
*/
enum ProfileType {
POSITIVE, NEGATIVE
}
/**
* Base class for profile matchers.
*/
private abstract static class ProfilesMatcher {
public final YamlProcessor.MatchStatus matches(Set<String> profiles) {
if (CollectionUtils.isEmpty(profiles)) {
return YamlProcessor.MatchStatus.ABSTAIN;
}
return doMatches(profiles);
}
protected abstract YamlProcessor.MatchStatus doMatches(Set<String> profiles);
}
/**
* {@link ProfilesMatcher} that matches when a value in {@code spring.profiles}
* is also in {@code spring.profiles.active}.
*/
private static class ActiveProfilesMatcher extends ProfilesMatcher {
private final Set<String> activeProfiles;
ActiveProfilesMatcher(Set<String> activeProfiles){
this.activeProfiles = activeProfiles;
}
@Override
protected YamlProcessor.MatchStatus doMatches(Set<String> profiles) {
if (profiles.isEmpty()) {
return YamlProcessor.MatchStatus.NOT_FOUND;
}
for (String activeProfile : this.activeProfiles) {
if (profiles.contains(activeProfile)) {
return YamlProcessor.MatchStatus.FOUND;
}
}
return YamlProcessor.MatchStatus.NOT_FOUND;
}
}
/**
* {@link ProfilesMatcher} that matches when {@code
* spring.profiles} is empty or contains a value with no text.
*
* @see StringUtils#hasText(String)
*/
private static class EmptyProfilesMatcher extends ProfilesMatcher {
@Override
public YamlProcessor.MatchStatus doMatches(Set<String> springProfiles) {
if (springProfiles.isEmpty()) {
return YamlProcessor.MatchStatus.FOUND;
}
for (String profile : springProfiles) {
if (!StringUtils.hasText(profile)) {
return YamlProcessor.MatchStatus.FOUND;
}
}
return YamlProcessor.MatchStatus.NOT_FOUND;
}
}
/**
* Class for binding {@code spring.profiles} property.
*/
static class SpringProperties {
private List<String> profiles = new ArrayList<String>();
public List<String> getProfiles() {
return this.profiles;
}
public void setProfiles(List<String> profiles) {
this.profiles = profiles;
}
}
}
@@ -1,420 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.common;
import java.io.IOException;
import java.io.Reader;
import java.util.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.parser.ParserException;
import org.yaml.snakeyaml.reader.UnicodeReader;
/**
* Base class for YAML factories.
*
* @author Dave Syer
* @author Juergen Hoeller
* @since 4.1
*/
public abstract class YamlProcessor {
private final Log logger = LogFactory.getLog(getClass());
private ResolutionMethod resolutionMethod = ResolutionMethod.OVERRIDE;
private Resource[] resources = new Resource[0];
private List<DocumentMatcher> documentMatchers = Collections.emptyList();
private boolean matchDefault = true;
/**
* A map of document matchers allowing callers to selectively use only some of
* the documents in a YAML resource. In YAML documents are separated by
* <code>---<code> lines, and each document is converted to properties before
* the match is made. E.g.
*
* <pre class="code">
* environment: dev
* url: http://dev.bar.com
* name: Developer Setup
* ---
* environment: prod
* url:http://foo.bar.com
* name: My Cool App
* </pre>
*
* when mapped with
*
* <pre class="code">
* setDocumentMatchers(properties -> ("prod"
* .equals(properties.getProperty("environment")) ? MatchStatus.FOUND : MatchStatus.NOT_FOUND));
* </pre>
*
* would end up as
*
* <pre class="code">
* environment=prod
* url=http://foo.bar.com
* name=My Cool App
* </pre>
*/
public void setDocumentMatchers(DocumentMatcher... matchers) {
this.documentMatchers = Arrays.asList(matchers);
}
/**
* Flag indicating that a document for which all the
* {@link #setDocumentMatchers(DocumentMatcher...) document matchers} abstain
* will nevertheless match. Default is {@code true}.
*/
public void setMatchDefault(boolean matchDefault) {
this.matchDefault = matchDefault;
}
/**
* Method to use for resolving resources. Each resource will be converted to a
* Map, so this property is used to decide which map entries to keep in the
* final output from this factory. Default is {@link ResolutionMethod#OVERRIDE}.
*/
public void setResolutionMethod(ResolutionMethod resolutionMethod) {
Assert.notNull(resolutionMethod, "ResolutionMethod must not be null");
this.resolutionMethod = resolutionMethod;
}
/**
* Set locations of YAML {@link Resource resources} to be loaded.
*
* @see ResolutionMethod
*/
public void setResources(Resource... resources) {
this.resources = resources;
}
/**
* Provide an opportunity for subclasses to process the Yaml parsed from the
* supplied resources. Each resource is parsed in turn and the documents inside
* checked against the {@link #setDocumentMatchers(DocumentMatcher...)
* matchers}. If a document matches it is passed into the callback, along with
* its representation as Properties. Depending on the
* {@link #setResolutionMethod(ResolutionMethod)} not all of the documents will
* be parsed.
*
* @param callback a callback to delegate to once matching documents are found
* @see #createYaml()
*/
protected void process(MatchCallback callback) {
Yaml yaml = createYaml();
for (Resource resource : this.resources) {
boolean found = process(callback, yaml, resource);
if (this.resolutionMethod == ResolutionMethod.FIRST_FOUND && found) {
return;
}
}
}
/**
* Create the {@link Yaml} instance to use.
*/
protected Yaml createYaml() {
return new Yaml(new StrictMapAppenderConstructor());
}
private boolean process(MatchCallback callback, Yaml yaml, Resource resource) {
int count = 0;
try {
if (logger.isDebugEnabled()) {
logger.debug("Loading from YAML: " + resource);
}
Reader reader = new UnicodeReader(resource.getInputStream());
try {
for (Object object : yaml.loadAll(reader)) {
if (object != null && process(asMap(object), callback)) {
count++;
if (this.resolutionMethod == ResolutionMethod.FIRST_FOUND) {
break;
}
}
}
if (logger.isDebugEnabled()) {
logger.debug(
"Loaded " + count + " document" + (count > 1 ? "s" : "") + " from YAML resource: " + resource);
}
} finally {
reader.close();
}
} catch (IOException ex) {
handleProcessError(resource, ex);
}
return (count > 0);
}
private void handleProcessError(Resource resource, IOException ex) {
if (this.resolutionMethod != ResolutionMethod.FIRST_FOUND
&& this.resolutionMethod != ResolutionMethod.OVERRIDE_AND_IGNORE) {
throw new IllegalStateException(ex);
}
if (logger.isWarnEnabled()) {
logger.warn("Could not load map from " + resource + ": " + ex.getMessage());
}
}
@SuppressWarnings("unchecked")
private Map<String, Object> asMap(Object object) {
// YAML can have numbers as keys
Map<String, Object> result = new LinkedHashMap<String, Object>();
if (!(object instanceof Map)) {
// A document can be a text literal
result.put("document", object);
return result;
}
Map<Object, Object> map = (Map<Object, Object>) object;
for (Map.Entry<Object, Object> entry : map.entrySet()) {
Object value = entry.getValue();
if (value instanceof Map) {
value = asMap(value);
}
Object key = entry.getKey();
if (key instanceof CharSequence) {
result.put(key.toString(), value);
} else {
// It has to be a map key in this case
result.put("[" + key.toString() + "]", value);
}
}
return result;
}
private boolean process(Map<String, Object> map, MatchCallback callback) {
@SuppressWarnings("serial")
Properties properties = new Properties() {
@Override
public String getProperty(String key) {
Object value = get(key);
return (value != null ? value.toString() : null);
}
};
properties.putAll(getFlattenedMap(map));
if (this.documentMatchers.isEmpty()) {
if (logger.isDebugEnabled()) {
logger.debug("Merging document (no matchers set): " + map);
}
callback.process(properties, map);
return true;
}
MatchStatus result = MatchStatus.ABSTAIN;
for (DocumentMatcher matcher : this.documentMatchers) {
MatchStatus match = matcher.matches(properties);
result = MatchStatus.getMostSpecific(match, result);
if (match == MatchStatus.FOUND) {
if (logger.isDebugEnabled()) {
logger.debug("Matched document with document matcher: " + properties);
}
callback.process(properties, map);
return true;
}
}
if (result == MatchStatus.ABSTAIN && this.matchDefault) {
if (logger.isDebugEnabled()) {
logger.debug("Matched document with default matcher: " + map);
}
callback.process(properties, map);
return true;
}
if (logger.isDebugEnabled()) {
logger.debug("Unmatched document: " + map);
}
return false;
}
/**
* Return a flattened version of the given map, recursively following any nested
* Map or Collection values. Entries from the resulting map retain the same
* order as the source. When called with the Map from a {@link MatchCallback}
* the result will contain the same values as the {@link MatchCallback}
* Properties.
*
* @param source the source map
* @return a flattened map
* @since 4.1.3
*/
protected final Map<String, Object> getFlattenedMap(Map<String, Object> source) {
Map<String, Object> result = new LinkedHashMap<String, Object>();
buildFlattenedMap(result, source, null);
return result;
}
private static boolean containsText(CharSequence str) {
int strLen = str.length();
for (int i = 0; i < strLen; i++) {
if (!Character.isWhitespace(str.charAt(i))) {
return true;
}
}
return false;
}
private void buildFlattenedMap(Map<String, Object> result, Map<String, Object> source, String path) {
for (Map.Entry<String, Object> entry : source.entrySet()) {
String key = entry.getKey();
if (path != null && !path.isEmpty() && containsText(path)) {
if (key.startsWith("[")) {
key = path + key;
} else {
key = path + '.' + key;
}
}
Object value = entry.getValue();
if (value instanceof String) {
result.put(key, value);
} else if (value instanceof Map) {
// Need a compound key
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) value;
buildFlattenedMap(result, map, key);
} else if (value instanceof Collection) {
// Need a compound key
@SuppressWarnings("unchecked")
Collection<Object> collection = (Collection<Object>) value;
int count = 0;
for (Object object : collection) {
buildFlattenedMap(result, Collections.singletonMap("[" + (count++) + "]", object), key);
}
} else {
result.put(key, (value != null ? value : ""));
}
}
}
/**
* Callback interface used to process the YAML parsing results.
*/
public interface MatchCallback {
/**
* Process the given representation of the parsing results.
*
* @param properties the properties to process (as a flattened representation
* with indexed keys in case of a collection or map)
* @param map the result map (preserving the original value structure in the
* YAML document)
*/
void process(Properties properties, Map<String, Object> map);
}
/**
* Strategy interface used to test if properties match.
*/
public interface DocumentMatcher {
/**
* Test if the given properties match.
*
* @param properties the properties to test
* @return the status of the match
*/
MatchStatus matches(Properties properties);
}
/**
* Status returned from {@link DocumentMatcher#matches(Properties)}
*/
public enum MatchStatus {
/**
* A match was found.
*/
FOUND,
/**
* No match was found.
*/
NOT_FOUND,
/**
* The matcher should not be considered.
*/
ABSTAIN;
/**
* Compare two {@link MatchStatus} items, returning the most specific status.
*/
public static MatchStatus getMostSpecific(MatchStatus a, MatchStatus b) {
return (a.ordinal() < b.ordinal() ? a : b);
}
}
/**
* Method to use for resolving resources.
*/
public enum ResolutionMethod {
/**
* Replace values from earlier in the list.
*/
OVERRIDE,
/**
* Replace values from earlier in the list, ignoring any failures.
*/
OVERRIDE_AND_IGNORE,
/**
* Take the first resource in the list that exists and use just that.
*/
FIRST_FOUND
}
/**
* A specialized {@link Constructor} that checks for duplicate keys.
*/
protected static class StrictMapAppenderConstructor extends Constructor {
// Declared as public for use in subclasses
public StrictMapAppenderConstructor(){
super();
}
@Override
protected Map<Object, Object> constructMapping(MappingNode node) {
try {
return super.constructMapping(node);
} catch (IllegalStateException ex) {
throw new ParserException("while parsing MappingNode",
node.getStartMark(),
ex.getMessage(),
node.getEndMark());
}
}
@Override
protected Map<Object, Object> createDefaultMap() {
final Map<Object, Object> delegate = super.createDefaultMap();
return new AbstractMap<Object, Object>() {
@Override
public Object put(Object key, Object value) {
if (delegate.containsKey(key)) {
throw new IllegalStateException("Duplicate key: " + key);
}
return delegate.put(key, value);
}
@Override
public Set<Entry<Object, Object>> entrySet() {
return delegate.entrySet();
}
};
}
}
}
@@ -1,87 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.common;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Pattern;
import org.springframework.util.ClassUtils;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.nodes.Tag;
import org.yaml.snakeyaml.representer.Representer;
import org.yaml.snakeyaml.resolver.Resolver;
/**
* Strategy to load '.yml' (or '.yaml') files into a {@link PropertySource}.
*
* @author Dave Syer
* @author Phillip Webb
* @author Andy Wilkinson
*/
public class YamlPropertySourceLoader implements PropertySourceLoader {
@Override
public String[] getFileExtensions() {
return new String[] { "yml", "yaml" };
}
@Override
public PropertySource<?> load(String name, Resource resource, String profile) throws IOException {
if (ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", null)) {
Processor processor = new Processor(resource, profile);
Map<String, Object> source = processor.process();
if (!source.isEmpty()) {
return new MapPropertySource(name, source);
}
}
return null;
}
/**
* {@link YamlProcessor} to create a {@link Map} containing the property values.
* Similar to {@link YamlPropertiesFactoryBean} but retains the order of
* entries.
*/
private static class Processor extends YamlProcessor {
Processor(Resource resource, String profile){
if (profile == null) {
setMatchDefault(true);
setDocumentMatchers(new SpringProfileDocumentMatcher());
} else {
setMatchDefault(false);
setDocumentMatchers(new SpringProfileDocumentMatcher(profile));
}
setResources(resource);
}
@Override
protected Yaml createYaml() {
return new Yaml(new StrictMapAppenderConstructor(), new Representer(), new DumperOptions(), new Resolver() {
@Override
public void addImplicitResolver(Tag tag, Pattern regexp, String first) {
if (tag == Tag.TIMESTAMP) {
return;
}
super.addImplicitResolver(tag, regexp, first);
}
});
}
public Map<String, Object> process() {
final Map<String, Object> result = new LinkedHashMap<String, Object>();
process(new MatchCallback() {
@Override
public void process(Properties properties, Map<String, Object> map) {
result.putAll(getFlattenedMap(map));
}
});
return result;
}
}
}
@@ -1,121 +0,0 @@
package com.alibaba.otter.canal.client.adapter.support;
import com.alibaba.druid.pool.DruidDataSource;
import com.google.common.base.Joiner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicLong;
public abstract class AbstractEtlService {
protected Logger logger = LoggerFactory.getLogger(this.getClass());
private String type;
private AdapterConfig config;
private final long CNT_PER_TASK = 10000L;
public AbstractEtlService(String type, AdapterConfig config){
this.type = type;
this.config = config;
}
protected EtlResult importData(String sql, List<String> params) {
EtlResult etlResult = new EtlResult();
AtomicLong impCount = new AtomicLong();
List<String> errMsg = new ArrayList<>();
if (config == null) {
logger.warn("{} mapping config is null, etl go end ", type);
etlResult.setErrorMessage(type + "mapping config is null, etl go end ");
return etlResult;
}
long start = System.currentTimeMillis();
try {
DruidDataSource dataSource = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
List<Object> values = new ArrayList<>();
// 拼接条件
if (config.getMapping().getEtlCondition() != null && params != null) {
String etlCondition = config.getMapping().getEtlCondition();
int size = params.size();
for (String param : params) {
etlCondition = etlCondition.replace("{}", "?");
values.add(param);
}
sql += " " + etlCondition;
}
if (logger.isDebugEnabled()) {
logger.debug("etl sql : {}", sql);
}
// 获取总数
String countSql = "SELECT COUNT(1) FROM ( " + sql + ") _CNT ";
long cnt = (Long) Util.sqlRS(dataSource, countSql, values, rs -> {
Long count = null;
try {
if (rs.next()) {
count = ((Number) rs.getObject(1)).longValue();
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return count == null ? 0L : count;
});
// 当大于1万条记录时开启多线程
if (cnt >= 10000) {
int threadCount = Runtime.getRuntime().availableProcessors();
long offset;
long size = CNT_PER_TASK;
long workerCnt = cnt / size + (cnt % size == 0 ? 0 : 1);
if (logger.isDebugEnabled()) {
logger.debug("workerCnt {} for cnt {} threadCount {}", workerCnt, cnt, threadCount);
}
ExecutorService executor = Util.newFixedThreadPool(threadCount, 5000L);
List<Future<Boolean>> futures = new ArrayList<>();
for (long i = 0; i < workerCnt; i++) {
offset = size * i;
String sqlFinal = sql + " LIMIT " + offset + "," + size;
Future<Boolean> future = executor.submit(
() -> executeSqlImport(dataSource, sqlFinal, values, config.getMapping(), impCount, errMsg));
futures.add(future);
}
for (Future<Boolean> future : futures) {
future.get();
}
executor.shutdown();
} else {
executeSqlImport(dataSource, sql, values, config.getMapping(), impCount, errMsg);
}
logger.info("数据全量导入完成, 一共导入 {} 条数据, 耗时: {}", impCount.get(), System.currentTimeMillis() - start);
etlResult.setResultMessage("导入" + type + " 数据:" + impCount.get() + "");
} catch (Exception e) {
logger.error(e.getMessage(), e);
errMsg.add(type + " 数据导入异常 =>" + e.getMessage());
}
if (errMsg.isEmpty()) {
etlResult.setSucceeded(true);
} else {
etlResult.setErrorMessage(Joiner.on("\n").join(errMsg));
}
return etlResult;
}
protected abstract boolean executeSqlImport(DataSource ds, String sql, List<Object> values,
AdapterConfig.AdapterMapping mapping, AtomicLong impCount,
List<String> errMsg);
}
@@ -1,11 +0,0 @@
package com.alibaba.otter.canal.client.adapter.support;
public interface AdapterConfig {
String getDataSourceKey();
AdapterMapping getMapping();
interface AdapterMapping {
String getEtlCondition();
}
}
@@ -1,231 +0,0 @@
package com.alibaba.otter.canal.client.adapter.support;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 配置信息类
*
* @author rewerma 2018-8-18 下午10:40:12
* @version 1.0.0
*/
public class CanalClientConfig {
// 单机模式下canal server的ip:port
private String canalServerHost;
// 集群模式下的zk地址,如果配置了单机地址则以单机为准!!
private String zookeeperHosts;
// kafka or rocket mq 地址
private String mqServers;
// 是否已flatMessage模式传输,只适用于mq模式
private Boolean flatMessage = true;
// 批大小
private Integer batchSize;
// 同步分批提交大小
private Integer syncBatchSize = 1000;
// 重试次数
private Integer retries;
// 消费超时时间
private Long timeout;
// 模式 tcp kafka rocketMQ
private String mode = "tcp";
// aliyun ak/sk
private String accessKey;
private String secretKey;
// 是否启用消息轨迹
private boolean enableMessageTrace;
// 在使用阿里云商业化mq服务时,如果想使用云上消息轨迹功能,请设置此配置为true
private String accessChannel;
// 用于使用开源RocketMQ时,设置自定义的消息轨迹topic
private String customizedTraceTopic;
// 开源RocketMQ命名空间
private String namespace;
// canal adapters 配置
private List<CanalAdapter> canalAdapters;
public String getCanalServerHost() {
return canalServerHost;
}
public void setCanalServerHost(String canalServerHost) {
this.canalServerHost = canalServerHost;
}
public String getZookeeperHosts() {
return zookeeperHosts;
}
public void setZookeeperHosts(String zookeeperHosts) {
this.zookeeperHosts = zookeeperHosts;
}
public String getMqServers() {
return mqServers;
}
public void setMqServers(String mqServers) {
this.mqServers = mqServers;
}
public Boolean getFlatMessage() {
return flatMessage;
}
public void setFlatMessage(Boolean flatMessage) {
this.flatMessage = flatMessage;
}
public Integer getBatchSize() {
return batchSize;
}
public void setBatchSize(Integer batchSize) {
this.batchSize = batchSize;
}
public Integer getRetries() {
return retries;
}
public Integer getSyncBatchSize() {
return syncBatchSize;
}
public void setSyncBatchSize(Integer syncBatchSize) {
this.syncBatchSize = syncBatchSize;
}
public void setRetries(Integer retries) {
this.retries = retries;
}
public Long getTimeout() {
return timeout;
}
public void setTimeout(Long timeout) {
this.timeout = timeout;
}
public String getMode() {
return mode;
}
public void setMode(String mode) {
this.mode = mode;
}
public String getAccessKey() {
return accessKey;
}
public void setAccessKey(String accessKey) {
this.accessKey = accessKey;
}
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public List<CanalAdapter> getCanalAdapters() {
return canalAdapters;
}
public void setCanalAdapters(List<CanalAdapter> canalAdapters) {
this.canalAdapters = canalAdapters;
}
public boolean isEnableMessageTrace() {
return enableMessageTrace;
}
public void setEnableMessageTrace(boolean enableMessageTrace) {
this.enableMessageTrace = enableMessageTrace;
}
public String getAccessChannel() {
return accessChannel;
}
public void setAccessChannel(String accessChannel) {
this.accessChannel = accessChannel;
}
public String getCustomizedTraceTopic() {
return customizedTraceTopic;
}
public void setCustomizedTraceTopic(String customizedTraceTopic) {
this.customizedTraceTopic = customizedTraceTopic;
}
public String getNamespace() {
return namespace;
}
public void setNamespace(String namespace) {
this.namespace = namespace;
}
public static class CanalAdapter {
private String instance; // 实例名
private List<Group> groups; // 适配器分组列表
public String getInstance() {
return instance;
}
public void setInstance(String instance) {
if (instance != null) {
this.instance = instance.trim();
}
}
public List<Group> getGroups() {
return groups;
}
public void setGroups(List<Group> groups) {
this.groups = groups;
}
}
public static class Group {
// group id
private String groupId = "default";
private List<OuterAdapterConfig> outerAdapters; // 适配器列表
private Map<String, OuterAdapterConfig> outerAdaptersMap = new LinkedHashMap<>();
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public List<OuterAdapterConfig> getOuterAdapters() {
return outerAdapters;
}
public void setOuterAdapters(List<OuterAdapterConfig> outerAdapters) {
this.outerAdapters = outerAdapters;
}
public Map<String, OuterAdapterConfig> getOuterAdaptersMap() {
return outerAdaptersMap;
}
public void setOuterAdaptersMap(Map<String, OuterAdapterConfig> outerAdaptersMap) {
this.outerAdaptersMap = outerAdaptersMap;
}
}
}
@@ -1,6 +0,0 @@
package com.alibaba.otter.canal.client.adapter.support;
public class Constant {
public static final String CONF_DIR = "conf";
}
@@ -1,81 +0,0 @@
package com.alibaba.otter.canal.client.adapter.support;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.alibaba.druid.pool.DruidDataSource;
/**
* 数据源配置
*
* @author rewerma @ 2018-10-20
* @version 1.0.0
*/
public class DatasourceConfig {
public final static Map<String, DruidDataSource> DATA_SOURCES = new ConcurrentHashMap<>(); // key对应的数据源
private String driver = "com.mysql.jdbc.Driver"; // 默认为mysql jdbc驱动
private String url; // jdbc url
private String database; // jdbc database
private String type = "mysql"; // 类型, 默认为mysql
private String username; // jdbc username
private String password; // jdbc password
private Integer maxActive = 3; // 连接池最大连接数,默认为3
public String getDriver() {
return driver;
}
public void setDriver(String driver) {
this.driver = driver;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getDatabase() {
return database;
}
public void setDatabase(String database) {
this.database = database;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getMaxActive() {
return maxActive;
}
public void setMaxActive(Integer maxActive) {
this.maxActive = maxActive;
}
}
@@ -1,145 +0,0 @@
package com.alibaba.otter.canal.client.adapter.support;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
/**
* DML操作转换对象
*
* @author rewerma 2018-8-19 下午11:30:49
* @version 1.0.0
*/
public class Dml implements Serializable {
private static final long serialVersionUID = 2611556444074013268L;
private String destination; // 对应canal的实例或者MQ的topic
private String groupId; // 对应mq的group id
private String database; // 数据库或schema
private String table; // 表名
private List<String> pkNames;
private Boolean isDdl;
private String type; // 类型: INSERT UPDATE DELETE
// binlog executeTime
private Long es; // 执行耗时
// dml build timeStamp
private Long ts; // 同步时间
private String sql; // 执行的sql, dml sql为空
private List<Map<String, Object>> data; // 数据列表
private List<Map<String, Object>> old; // 旧数据列表, 用于update, size和data的size一一对应
public String getDestination() {
return destination;
}
public void setDestination(String destination) {
this.destination = destination;
}
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public String getDatabase() {
return database;
}
public void setDatabase(String database) {
this.database = database;
}
public String getTable() {
return table;
}
public void setTable(String table) {
this.table = table;
}
public List<String> getPkNames() {
return pkNames;
}
public void setPkNames(List<String> pkNames) {
this.pkNames = pkNames;
}
public Boolean getIsDdl() {
return isDdl;
}
public void setIsDdl(Boolean isDdl) {
this.isDdl = isDdl;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Long getTs() {
return ts;
}
public void setTs(Long ts) {
this.ts = ts;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
public List<Map<String, Object>> getData() {
return data;
}
public void setData(List<Map<String, Object>> data) {
this.data = data;
}
public List<Map<String, Object>> getOld() {
return old;
}
public void setOld(List<Map<String, Object>> old) {
this.old = old;
}
public Long getEs() {
return es;
}
public void setEs(Long es) {
this.es = es;
}
public void clear() {
database = null;
table = null;
type = null;
ts = null;
es = null;
data = null;
old = null;
sql = null;
}
@Override
public String toString() {
return "Dml{" + "destination='" + destination + '\'' + ", database='" + database + '\'' + ", table='" + table
+ '\'' + ", type='" + type + '\'' + ", es=" + es + ", ts=" + ts + ", sql='" + sql + '\'' + ", data="
+ data + ", old=" + old + '}';
}
}
@@ -1,44 +0,0 @@
package com.alibaba.otter.canal.client.adapter.support;
import java.io.Serializable;
/**
* ETL的结果对象
*
* @author rewerma @ 2018-10-20
* @version 1.0.0
*/
public class EtlResult implements Serializable {
private static final long serialVersionUID = 4250522736289866505L;
private boolean succeeded = false;
private String resultMessage;
private String errorMessage;
public boolean getSucceeded() {
return succeeded;
}
public void setSucceeded(boolean succeeded) {
this.succeeded = succeeded;
}
public String getResultMessage() {
return resultMessage;
}
public void setResultMessage(String resultMessage) {
this.resultMessage = resultMessage;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
}
@@ -1,427 +0,0 @@
package com.alibaba.otter.canal.client.adapter.support;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* SPI 类加载器
*
* @author rewerma 2018-8-19 下午11:30:49
* @version 1.0.0
*/
public class ExtensionLoader<T> {
private static final Logger logger = LoggerFactory
.getLogger(ExtensionLoader.class);
private static final String SERVICES_DIRECTORY = "META-INF/services/";
private static final String CANAL_DIRECTORY = "META-INF/canal/";
private static final String DEFAULT_CLASSLOADER_POLICY = "internal";
private static final Pattern NAME_SEPARATOR = Pattern
.compile("\\s*[,]+\\s*");
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();
private static final ConcurrentMap<String, Object> EXTENSION_KEY_INSTANCE = new ConcurrentHashMap<>();
private final Class<?> type;
private final String classLoaderPolicy;
private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
private String cachedDefaultName;
private ConcurrentHashMap<String, IllegalStateException> exceptions = new ConcurrentHashMap<>();
private static <T> boolean withExtensionAnnotation(Class<T> type) {
return type.isAnnotationPresent(SPI.class);
}
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
return getExtensionLoader(type, DEFAULT_CLASSLOADER_POLICY);
}
@SuppressWarnings("unchecked")
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type, String classLoaderPolicy) {
if (type == null) throw new IllegalArgumentException("Extension type == null");
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
}
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type(" + type + ") is not extension, because WITHOUT @"
+ SPI.class.getSimpleName() + " Annotation!");
}
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type, classLoaderPolicy));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
private ExtensionLoader(Class<?> type, String classLoaderPolicy){
this.type = type;
this.classLoaderPolicy = classLoaderPolicy;
}
/**
* 返回指定名字的扩展
*
* @param name
* @return
*/
@SuppressWarnings("unchecked")
public T getExtension(String name) {
if (name == null || name.length() == 0) throw new IllegalArgumentException("Extension name == null");
if ("true".equals(name)) {
return getDefaultExtension();
}
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<>());
holder = cachedInstances.get(name);
}
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
@SuppressWarnings("unchecked")
public T getExtension(String name, String key) {
if (name == null || name.length() == 0) throw new IllegalArgumentException("Extension name == null");
if ("true".equals(name)) {
return getDefaultExtension();
}
String extKey = name + "-" + StringUtils.trimToEmpty(key);
Holder<Object> holder = cachedInstances.get(extKey);
if (holder == null) {
cachedInstances.putIfAbsent(extKey, new Holder<>());
holder = cachedInstances.get(extKey);
}
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name, key);
holder.set(instance);
}
}
}
return (T) instance;
}
/**
* 返回缺省的扩展,如果没有设置则返回<code>null</code>
*/
public T getDefaultExtension() {
getExtensionClasses();
if (null == cachedDefaultName || cachedDefaultName.length() == 0 || "true".equals(cachedDefaultName)) {
return null;
}
return getExtension(cachedDefaultName);
}
@SuppressWarnings("unchecked")
private T createExtension(String name) {
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type
+ ") could not be instantiated: class could not be found");
}
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type
+ ") could not be instantiated: " + t.getMessage(),
t);
}
}
@SuppressWarnings("unchecked")
private T createExtension(String name, String key) {
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type
+ ") could not be instantiated: class could not be found");
}
try {
T instance = (T) EXTENSION_KEY_INSTANCE.get(name + "-" + key);
if (instance == null) {
EXTENSION_KEY_INSTANCE.putIfAbsent(name + "-" + key, clazz.newInstance());
instance = (T) EXTENSION_KEY_INSTANCE.get(name + "-" + key);
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type
+ ") could not be instantiated: " + t.getMessage(),
t);
}
}
private Map<String, Class<?>> getExtensionClasses() {
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}
private String getJarDirectoryPath() {
URL url = Thread.currentThread().getContextClassLoader().getResource("");
String dirtyPath;
if (url != null) {
dirtyPath = url.toString();
} else {
File file = new File("");
dirtyPath = file.getAbsolutePath();
}
String jarPath = dirtyPath.replaceAll("^.*file:/", ""); // removes
// file:/ and
// everything
// before it
jarPath = jarPath.replaceAll("jar!.*", "jar"); // removes everything
// after .jar, if .jar
// exists in dirtyPath
jarPath = jarPath.replaceAll("%20", " "); // necessary if path has
// spaces within
if (!jarPath.endsWith(".jar")) { // this is needed if you plan to run
// the app using Spring Tools Suit play
// button.
jarPath = jarPath.replaceAll("/classes/.*", "/classes/");
}
Path path = Paths.get(jarPath).getParent(); // Paths - from java 8
if (path != null) {
return path.toString();
}
return null;
}
private Map<String, Class<?>> loadExtensionClasses() {
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation != null) {
String value = defaultAnnotation.value();
if ((value = value.trim()).length() > 0) {
String[] names = NAME_SEPARATOR.split(value);
if (names.length > 1) {
throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
+ ": " + Arrays.toString(names));
}
if (names.length == 1) cachedDefaultName = names[0];
}
}
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
// 1. plugin foldercustomized extension classLoader jar_dir/plugin
String dir = File.separator + this.getJarDirectoryPath() + File.separator + "plugin";
File externalLibDir = new File(dir);
if (!externalLibDir.exists()) {
externalLibDir = new File(File.separator + this.getJarDirectoryPath() + File.separator + "canal-adapter"
+ File.separator + "plugin");
}
logger.info("extension classpath dir: " + externalLibDir.getAbsolutePath());
if (externalLibDir.exists()) {
File[] files = externalLibDir.listFiles((dir1, name) -> name.endsWith(".jar"));
if (files != null) {
for (File f : files) {
URL url;
try {
url = f.toURI().toURL();
} catch (MalformedURLException e) {
throw new RuntimeException("load extension jar failed!", e);
}
ClassLoader parent = Thread.currentThread().getContextClassLoader();
URLClassLoader localClassLoader;
if (classLoaderPolicy == null || "".equals(classLoaderPolicy)
|| DEFAULT_CLASSLOADER_POLICY.equalsIgnoreCase(classLoaderPolicy)) {
localClassLoader = new URLClassExtensionLoader(new URL[] { url });
} else {
localClassLoader = new URLClassLoader(new URL[] { url }, parent);
}
loadFile(extensionClasses, CANAL_DIRECTORY, localClassLoader);
loadFile(extensionClasses, SERVICES_DIRECTORY, localClassLoader);
}
}
}
// 只加载外部spi, 不加载classpath
// 2. load inner extension class with default classLoader
// ClassLoader classLoader = findClassLoader();
// loadFile(extensionClasses, CANAL_DIRECTORY, classLoader);
// loadFile(extensionClasses, SERVICES_DIRECTORY, classLoader);
return extensionClasses;
}
private void loadFile(Map<String, Class<?>> extensionClasses, String dir, ClassLoader classLoader) {
String fileName = dir + type.getName();
try {
Enumeration<URL> urls;
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
if (urls != null) {
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
try {
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
String line = null;
while ((line = reader.readLine()) != null) {
final int ci = line.indexOf('#');
if (ci >= 0) line = line.substring(0, ci);
line = line.trim();
if (line.length() > 0) {
try {
String name = null;
int i = line.indexOf('=');
if (i > 0) {
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
Class<?> clazz = classLoader.loadClass(line);
// Class<?> clazz =
// Class.forName(line, true,
// classLoader);
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException(
"Error when load extension class(interface: " + type
+ ", class line: " + clazz.getName()
+ "), class " + clazz.getName()
+ "is not subtype of interface.");
} else {
try {
clazz.getConstructor(type);
} catch (NoSuchMethodException e) {
clazz.getConstructor();
String[] names = NAME_SEPARATOR.split(name);
if (names != null && names.length > 0) {
for (String n : names) {
if (!cachedNames.containsKey(clazz)) {
cachedNames.put(clazz, n);
}
Class<?> c = extensionClasses.get(n);
if (c == null) {
extensionClasses.put(n, clazz);
} else if (c != clazz) {
cachedNames.remove(clazz);
throw new IllegalStateException(
"Duplicate extension " + type.getName() + " name "
+ n + " on "
+ c.getName() + " and "
+ clazz.getName());
}
}
}
}
}
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException(
"Failed to load extension class(interface: " + type + ", class line: "
+ line + ") in " + url
+ ", cause: "
+ t.getMessage(),
t);
exceptions.put(line, e);
}
}
} // end of while read lines
} finally {
if (reader != null) {
reader.close();
}
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " + type + ", class file: " + url
+ ") in " + url,
t);
}
} // end of while urls
}
} catch (Throwable t) {
logger.error(
"Exception when load extension class(interface: " + type + ", description file: " + fileName + ").",
t);
}
}
@SuppressWarnings("unused")
private static ClassLoader findClassLoader() {
return ExtensionLoader.class.getClassLoader();
}
@Override
public String toString() {
return this.getClass().getName() + "[" + type.getName() + "]";
}
private static class Holder<T> {
private volatile T value;
private void set(T value) {
this.value = value;
}
private T get() {
return value;
}
}
}
@@ -1,170 +0,0 @@
package com.alibaba.otter.canal.client.adapter.support;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Date;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 类型转换工具类
*
* @author rewerma 2018-8-19 下午06:14:23
* @version 1.0.0
*/
public class JdbcTypeUtil {
private static Logger logger = LoggerFactory.getLogger(JdbcTypeUtil.class);
public static Object getRSData(ResultSet rs, String columnName, int jdbcType) throws SQLException {
if (jdbcType == Types.BIT || jdbcType == Types.BOOLEAN) {
return rs.getByte(columnName);
} else {
return rs.getObject(columnName);
}
}
public static Class<?> jdbcType2javaType(int jdbcType) {
switch (jdbcType) {
case Types.BIT:
case Types.BOOLEAN:
// return Boolean.class;
case Types.TINYINT:
return Byte.TYPE;
case Types.SMALLINT:
return Short.class;
case Types.INTEGER:
return Integer.class;
case Types.BIGINT:
return Long.class;
case Types.DECIMAL:
case Types.NUMERIC:
return BigDecimal.class;
case Types.REAL:
return Float.class;
case Types.FLOAT:
case Types.DOUBLE:
return Double.class;
case Types.CHAR:
case Types.VARCHAR:
case Types.LONGVARCHAR:
return String.class;
case Types.BINARY:
case Types.VARBINARY:
case Types.LONGVARBINARY:
case Types.BLOB:
return byte[].class;
case Types.DATE:
return java.sql.Date.class;
case Types.TIME:
return Time.class;
case Types.TIMESTAMP:
return Timestamp.class;
default:
return String.class;
}
}
private static boolean isText(String columnType) {
return "LONGTEXT".equalsIgnoreCase(columnType) || "MEDIUMTEXT".equalsIgnoreCase(columnType)
|| "TEXT".equalsIgnoreCase(columnType) || "TINYTEXT".equalsIgnoreCase(columnType);
}
public static Object typeConvert(String tableName ,String columnName, String value, int sqlType, String mysqlType) {
if (value == null
|| (value.equals("") && !(isText(mysqlType) || sqlType == Types.CHAR || sqlType == Types.VARCHAR || sqlType == Types.LONGVARCHAR))) {
return null;
}
try {
Object res;
switch (sqlType) {
case Types.INTEGER:
res = Integer.parseInt(value);
break;
case Types.SMALLINT:
res = Short.parseShort(value);
break;
case Types.BIT:
case Types.TINYINT:
res = Byte.parseByte(value);
break;
case Types.BIGINT:
if (mysqlType.startsWith("bigint") && mysqlType.endsWith("unsigned")) {
res = new BigInteger(value);
} else {
res = Long.parseLong(value);
}
break;
// case Types.BIT:
case Types.BOOLEAN:
res = !"0".equals(value);
break;
case Types.DOUBLE:
case Types.FLOAT:
res = Double.parseDouble(value);
break;
case Types.REAL:
res = Float.parseFloat(value);
break;
case Types.DECIMAL:
case Types.NUMERIC:
res = new BigDecimal(value);
break;
case Types.BINARY:
case Types.VARBINARY:
case Types.LONGVARBINARY:
case Types.BLOB:
res = value.getBytes("ISO-8859-1");
break;
case Types.DATE:
if (!value.startsWith("0000-00-00")) {
java.util.Date date = Util.parseDate(value);
if (date != null) {
res = new Date(date.getTime());
} else {
res = null;
}
} else {
res = null;
}
break;
case Types.TIME: {
java.util.Date date = Util.parseDate(value);
if (date != null) {
res = new Time(date.getTime());
} else {
res = null;
}
break;
}
case Types.TIMESTAMP:
if (!value.startsWith("0000-00-00")) {
java.util.Date date = Util.parseDate(value);
if (date != null) {
res = new Timestamp(date.getTime());
} else {
res = null;
}
} else {
res = null;
}
break;
case Types.CLOB:
default:
res = value;
break;
}
return res;
} catch (Exception e) {
logger.error("table: {} column: {}, failed convert type {} to {}", tableName, columnName, value, sqlType);
return value;
}
}
}
@@ -1,71 +0,0 @@
package com.alibaba.otter.canal.client.adapter.support;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public class MappingConfigsLoader {
public static Map<String, String> loadConfigs(String name) {
Map<String, String> configContentMap = new HashMap<>();
// 先取本地文件,再取类路径
File configDir = new File(".." + File.separator + Constant.CONF_DIR + File.separator + name);
if (!configDir.exists()) {
URL url = MappingConfigsLoader.class.getClassLoader().getResource("");
if (url != null) {
configDir = new File(url.getPath() + name + File.separator);
}
}
File[] files = configDir.listFiles();
if (files != null) {
for (File file : files) {
String fileName = file.getName();
if (!fileName.endsWith(".yml")) {
continue;
}
try (InputStream in = new FileInputStream(file)) {
byte[] bytes = new byte[in.available()];
in.read(bytes);
String configContent = new String(bytes, StandardCharsets.UTF_8);
configContentMap.put(fileName, configContent);
} catch (IOException e) {
throw new RuntimeException("Read " + name + "mapping config: " + fileName + " error. ", e);
}
}
}
return configContentMap;
}
public static String loadConfig(String name) {
// 先取本地文件,再取类路径
File filePath = new File(".." + File.separator + Constant.CONF_DIR + File.separator + name);
if (!filePath.exists()) {
URL url = MappingConfigsLoader.class.getClassLoader().getResource("");
if (url != null) {
filePath = new File(url.getPath() + name);
}
}
if (filePath.exists()) {
String fileName = filePath.getName();
if (!fileName.endsWith(".yml")) {
return null;
}
try (InputStream in = new FileInputStream(filePath)) {
byte[] bytes = new byte[in.available()];
in.read(bytes);
return new String(bytes, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new RuntimeException("Read mapping config: " + filePath.getAbsolutePath() + " error. ", e);
}
}
return null;
}
}
@@ -1,201 +0,0 @@
package com.alibaba.otter.canal.client.adapter.support;
import java.util.*;
import com.alibaba.otter.canal.protocol.CanalEntry;
import com.alibaba.otter.canal.protocol.FlatMessage;
import com.alibaba.otter.canal.protocol.Message;
/**
* Message对象解析工具类
*
* @author rewerma 2018-8-19 下午06:14:23
* @version 1.0.0
*/
public class MessageUtil {
public static List<Dml> parse4Dml(String destination, String groupId, Message message) {
if (message == null) {
return null;
}
List<CanalEntry.Entry> entries = message.getEntries();
List<Dml> dmls = new ArrayList<Dml>(entries.size());
for (CanalEntry.Entry entry : entries) {
if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN
|| entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) {
continue;
}
CanalEntry.RowChange rowChange;
try {
rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
} catch (Exception e) {
throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(),
e);
}
CanalEntry.EventType eventType = rowChange.getEventType();
final Dml dml = new Dml();
dml.setIsDdl(rowChange.getIsDdl());
dml.setDestination(destination);
dml.setGroupId(groupId);
dml.setDatabase(entry.getHeader().getSchemaName());
dml.setTable(entry.getHeader().getTableName());
dml.setType(eventType.toString());
dml.setEs(entry.getHeader().getExecuteTime());
dml.setIsDdl(rowChange.getIsDdl());
dml.setTs(System.currentTimeMillis());
dml.setSql(rowChange.getSql());
dmls.add(dml);
List<Map<String, Object>> data = new ArrayList<>();
List<Map<String, Object>> old = new ArrayList<>();
if (!rowChange.getIsDdl()) {
Set<String> updateSet = new HashSet<>();
dml.setPkNames(new ArrayList<>());
int i = 0;
for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
if (eventType != CanalEntry.EventType.INSERT && eventType != CanalEntry.EventType.UPDATE
&& eventType != CanalEntry.EventType.DELETE) {
continue;
}
Map<String, Object> row = new LinkedHashMap<>();
List<CanalEntry.Column> columns;
if (eventType == CanalEntry.EventType.DELETE) {
columns = rowData.getBeforeColumnsList();
} else {
columns = rowData.getAfterColumnsList();
}
for (CanalEntry.Column column : columns) {
if (i == 0) {
if (column.getIsKey()) {
dml.getPkNames().add(column.getName());
}
}
if (column.getIsNull()) {
row.put(column.getName(), null);
} else {
row.put(column.getName(),
JdbcTypeUtil.typeConvert(dml.getTable(),
column.getName(),
column.getValue(),
column.getSqlType(),
column.getMysqlType()));
}
// 获取update为true的字段
if (column.getUpdated()) {
updateSet.add(column.getName());
}
}
if (!row.isEmpty()) {
data.add(row);
}
if (eventType == CanalEntry.EventType.UPDATE) {
Map<String, Object> rowOld = new LinkedHashMap<>();
for (CanalEntry.Column column : rowData.getBeforeColumnsList()) {
if (updateSet.contains(column.getName())) {
if (column.getIsNull()) {
rowOld.put(column.getName(), null);
} else {
rowOld.put(column.getName(),
JdbcTypeUtil.typeConvert(dml.getTable(),
column.getName(),
column.getValue(),
column.getSqlType(),
column.getMysqlType()));
}
}
}
// update操作将记录修改前的值
if (!rowOld.isEmpty()) {
old.add(rowOld);
}
}
i++;
}
if (!data.isEmpty()) {
dml.setData(data);
}
if (!old.isEmpty()) {
dml.setOld(old);
}
}
}
return dmls;
}
public static List<Dml> flatMessage2Dml(String destination, String groupId, List<FlatMessage> flatMessages) {
List<Dml> dmls = new ArrayList<Dml>(flatMessages.size());
for (FlatMessage flatMessage : flatMessages) {
Dml dml = flatMessage2Dml(destination, groupId, flatMessage);
if (dml != null) {
dmls.add(dml);
}
}
return dmls;
}
public static Dml flatMessage2Dml(String destination, String groupId, FlatMessage flatMessage) {
if (flatMessage == null) {
return null;
}
Dml dml = new Dml();
dml.setDestination(destination);
dml.setGroupId(groupId);
dml.setDatabase(flatMessage.getDatabase());
dml.setTable(flatMessage.getTable());
dml.setPkNames(flatMessage.getPkNames());
dml.setIsDdl(flatMessage.getIsDdl());
dml.setType(flatMessage.getType());
dml.setTs(flatMessage.getTs());
dml.setEs(flatMessage.getEs());
dml.setSql(flatMessage.getSql());
// if (flatMessage.getSqlType() == null || flatMessage.getMysqlType() == null) {
// throw new RuntimeException("SqlType or mysqlType is null");
// }
List<Map<String, String>> data = flatMessage.getData();
if (data != null) {
dml.setData(changeRows(dml.getTable(), data, flatMessage.getSqlType(), flatMessage.getMysqlType()));
}
List<Map<String, String>> old = flatMessage.getOld();
if (old != null) {
dml.setOld(changeRows(dml.getTable(), old, flatMessage.getSqlType(), flatMessage.getMysqlType()));
}
return dml;
}
private static List<Map<String, Object>> changeRows(String table, List<Map<String, String>> rows,
Map<String, Integer> sqlTypes, Map<String, String> mysqlTypes) {
List<Map<String, Object>> result = new ArrayList<>();
for (Map<String, String> row : rows) {
Map<String, Object> resultRow = new LinkedHashMap<>();
for (Map.Entry<String, String> entry : row.entrySet()) {
String columnName = entry.getKey();
String columnValue = entry.getValue();
Integer sqlType = sqlTypes.get(columnName);
if (sqlType == null) {
continue;
}
String mysqlType = mysqlTypes.get(columnName);
if (mysqlType == null) {
continue;
}
Object finalValue = JdbcTypeUtil.typeConvert(table, columnName, columnValue, sqlType, mysqlType);
resultRow.put(columnName, finalValue);
}
result.add(resultRow);
}
return result;
}
}
@@ -1,62 +0,0 @@
package com.alibaba.otter.canal.client.adapter.support;
import java.util.Map;
/**
* 外部适配器配置信息类
*
* @author rewerma 2018-8-18 下午10:15:12
* @version 1.0.0
*/
public class OuterAdapterConfig {
private String name; // 适配器名称, 如: logger, hbase, es
private String key; // 适配器唯一键
private String hosts; // 适配器内部的地址, 比如对应es该参数可以填写es的server地址
private String zkHosts; // 适配器内部的ZK地址, 比如对应HBase该参数可以填写HBase对应的ZK地址
private Map<String, String> properties; // 其余参数, 可填写适配器中的所需的配置信息
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getHosts() {
return hosts;
}
public void setHosts(String hosts) {
this.hosts = hosts;
}
public Map<String, String> getProperties() {
return properties;
}
public void setProperties(Map<String, String> properties) {
this.properties = properties;
}
public String getZkHosts() {
return zkHosts;
}
public void setZkHosts(String zkHosts) {
this.zkHosts = zkHosts;
}
}
@@ -1,57 +0,0 @@
package com.alibaba.otter.canal.client.adapter.support;
import java.io.Serializable;
import java.util.Date;
/**
* 用于rest的结果返回对象
*
* @author rewerma @ 2018-10-20
* @version 1.0.0
*/
public class Result implements Serializable {
private static final long serialVersionUID = -3276409502352405716L;
private Integer code = 20000;
private Object data;
private String message;
private Date sysTime;
public static Result createSuccess(String message) {
Result result = new Result();
result.setMessage(message);
return result;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Date getSysTime() {
return sysTime;
}
public void setSysTime(Date sysTime) {
this.sysTime = sysTime;
}
}
@@ -1,22 +0,0 @@
package com.alibaba.otter.canal.client.adapter.support;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* SPI装载器注解
*
* @author rewerma @ 2018-10-20
* @version 1.0.0
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
public @interface SPI {
// Default SPI name
String value() default "";
}
@@ -1,88 +0,0 @@
package com.alibaba.otter.canal.client.adapter.support;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Enumeration;
import java.util.NoSuchElementException;
public class URLClassExtensionLoader extends URLClassLoader {
public URLClassExtensionLoader(URL[] urls) {
super(urls);
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
Class<?> c = findLoadedClass(name);
if (c != null) {
return c;
}
if (name.startsWith("java.") || name.startsWith("org.slf4j.")
|| name.startsWith("org.apache.logging")
|| name.startsWith("org.apache.commons.logging.")) {
// || name.startsWith("org.apache.hadoop."))
// {
c = super.loadClass(name);
}
if (c != null) return c;
try {
// 先加载jar内的class,可避免jar冲突
c = findClass(name);
} catch (ClassNotFoundException e) {
c = null;
}
if (c != null) {
return c;
}
return super.loadClass(name);
}
@Override
public Enumeration<URL> getResources(String name) throws IOException {
@SuppressWarnings("unchecked")
Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];
tmp[0] = findResources(name); // local class
// path first
// tmp[1] = super.getResources(name);
return new CompoundEnumeration<>(tmp);
}
private static class CompoundEnumeration<E> implements Enumeration<E> {
private Enumeration<E>[] enums;
private int index = 0;
public CompoundEnumeration(Enumeration<E>[] enums){
this.enums = enums;
}
private boolean next() {
while (this.index < this.enums.length) {
if (this.enums[this.index] != null && this.enums[this.index].hasMoreElements()) {
return true;
}
++this.index;
}
return false;
}
public boolean hasMoreElements() {
return this.next();
}
public E nextElement() {
if (!this.next()) {
throw new NoSuchElementException();
} else {
return this.enums[this.index].nextElement();
}
}
}
}
@@ -1,291 +0,0 @@
package com.alibaba.otter.canal.client.adapter.support;
import java.io.File;
import java.net.URL;
import java.sql.*;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import javax.sql.DataSource;
import org.apache.commons.lang.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
public class Util {
private static final Logger logger = LoggerFactory.getLogger(Util.class);
/**
* 通过DS执行sql
*/
public static Object sqlRS(DataSource ds, String sql, Function<ResultSet, Object> fun) {
try (Connection conn = ds.getConnection();
Statement stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) {
stmt.setFetchSize(Integer.MIN_VALUE);
try (ResultSet rs = stmt.executeQuery(sql)) {
return fun.apply(rs);
}
} catch (Exception e) {
logger.error("sqlRs has error, sql: {} ", sql);
throw new RuntimeException(e);
}
}
public static Object sqlRS(DataSource ds, String sql, List<Object> values, Function<ResultSet, Object> fun) {
try (Connection conn = ds.getConnection()) {
try (PreparedStatement pstmt = conn
.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)) {
pstmt.setFetchSize(Integer.MIN_VALUE);
if (values != null) {
for (int i = 0; i < values.size(); i++) {
pstmt.setObject(i + 1, values.get(i));
}
}
try (ResultSet rs = pstmt.executeQuery()) {
return fun.apply(rs);
}
}
} catch (Exception e) {
logger.error("sqlRs has error, sql: {} ", sql);
throw new RuntimeException(e);
}
}
/**
* sql执行获取resultSet
*
* @param conn sql connection
* @param sql sql
* @param consumer 回调方法
*/
public static void sqlRS(Connection conn, String sql, Consumer<ResultSet> consumer) {
try (Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql)) {
consumer.accept(rs);
} catch (SQLException e) {
logger.error(e.getMessage(), e);
}
}
public static File getConfDirPath() {
return getConfDirPath("");
}
public static File getConfDirPath(String subConf) {
URL url = Util.class.getClassLoader().getResource("");
String path;
if (url != null) {
path = url.getPath();
} else {
path = new File("").getAbsolutePath();
}
File file = null;
if (path != null) {
file = new File(
path + ".." + File.separator + Constant.CONF_DIR + File.separator + StringUtils.trimToEmpty(subConf));
if (!file.exists()) {
file = new File(path + StringUtils.trimToEmpty(subConf));
}
}
if (file == null || !file.exists()) {
throw new RuntimeException("Config dir not found.");
}
return file;
}
public static String cleanColumn(String column) {
if (column == null) {
return null;
}
if (column.contains("`")) {
column = column.replaceAll("`", "");
}
if (column.contains("'")) {
column = column.replaceAll("'", "");
}
if (column.contains("\"")) {
column = column.replaceAll("\"", "");
}
return column;
}
public static ThreadPoolExecutor newFixedThreadPool(int nThreads, long keepAliveTime) {
return new ThreadPoolExecutor(nThreads,
nThreads,
keepAliveTime,
TimeUnit.MILLISECONDS,
new SynchronousQueue<>(),
(r, exe) -> {
if (!exe.isShutdown()) {
try {
exe.getQueue().put(r);
} catch (InterruptedException e) {
// ignore
}
}
});
}
public static ThreadPoolExecutor newSingleThreadExecutor(long keepAliveTime) {
return new ThreadPoolExecutor(1,
1,
keepAliveTime,
TimeUnit.MILLISECONDS,
new SynchronousQueue<>(),
(r, exe) -> {
if (!exe.isShutdown()) {
try {
exe.getQueue().put(r);
} catch (InterruptedException e) {
// ignore
}
}
});
}
public final static String timeZone; // 当前时区
private static DateTimeZone dateTimeZone;
static {
TimeZone localTimeZone = TimeZone.getDefault();
int rawOffset = localTimeZone.getRawOffset();
String symbol = "+";
if (rawOffset < 0) {
symbol = "-";
}
rawOffset = Math.abs(rawOffset);
int offsetHour = rawOffset / 3600000;
int offsetMinute = rawOffset % 3600000 / 60000;
String hour = String.format("%1$02d", offsetHour);
String minute = String.format("%1$02d", offsetMinute);
timeZone = symbol + hour + ":" + minute;
dateTimeZone = DateTimeZone.forID(timeZone);
TimeZone.setDefault(TimeZone.getTimeZone("GMT" + timeZone));
}
/**
* 通用日期时间字符解析
*
* @param datetimeStr 日期时间字符串
* @return Date
*/
public static Date parseDate(String datetimeStr) {
if (StringUtils.isEmpty(datetimeStr)) {
return null;
}
datetimeStr = datetimeStr.trim();
if (datetimeStr.contains("-")) {
if (datetimeStr.contains(":")) {
datetimeStr = datetimeStr.replace(" ", "T");
}
} else if (datetimeStr.contains(":")) {
datetimeStr = "T" + datetimeStr;
}
DateTime dateTime = new DateTime(datetimeStr, dateTimeZone);
return dateTime.toDate();
}
private static LoadingCache<String, DateTimeFormatter> dateFormatterCache = CacheBuilder.newBuilder()
.build(new CacheLoader<String, DateTimeFormatter>() {
@Override
public DateTimeFormatter load(String key) {
return DateTimeFormatter.ofPattern(key);
}
});
public static Date parseDate2(String datetimeStr) {
if (StringUtils.isEmpty(datetimeStr)) {
return null;
}
try {
datetimeStr = datetimeStr.trim();
int len = datetimeStr.length();
if (datetimeStr.contains("-") && datetimeStr.contains(":") && datetimeStr.contains(".")) {
// 包含日期+时间+毫秒
// 取毫秒位数
int msLen = len - datetimeStr.indexOf(".") - 1;
StringBuilder ms = new StringBuilder();
for (int i = 0; i < msLen; i++) {
ms.append("S");
}
String formatter = "yyyy-MM-dd HH:mm:ss." + ms;
DateTimeFormatter dateTimeFormatter = dateFormatterCache.get(formatter);
LocalDateTime dateTime = LocalDateTime.parse(datetimeStr, dateTimeFormatter);
return Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant());
} else if (datetimeStr.contains("-") && datetimeStr.contains(":")) {
// 包含日期+时间
// 判断包含时间位数
int i = datetimeStr.indexOf(":");
i = datetimeStr.indexOf(":", i + 1);
String formatter;
if (i > -1) {
formatter = "yyyy-MM-dd HH:mm:ss";
} else {
formatter = "yyyy-MM-dd HH:mm";
}
DateTimeFormatter dateTimeFormatter = dateFormatterCache.get(formatter);
LocalDateTime dateTime = LocalDateTime.parse(datetimeStr, dateTimeFormatter);
return Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant());
} else if (datetimeStr.contains("-")) {
// 只包含日期
String formatter = "yyyy-MM-dd";
DateTimeFormatter dateTimeFormatter = dateFormatterCache.get(formatter);
LocalDate localDate = LocalDate.parse(datetimeStr, dateTimeFormatter);
return Date.from(localDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
} else if (datetimeStr.contains(":")) {
// 只包含时间
String formatter;
if (datetimeStr.contains(".")) {
// 包含毫秒
int msLen = len - datetimeStr.indexOf(".") - 1;
StringBuilder ms = new StringBuilder();
for (int i = 0; i < msLen; i++) {
ms.append("S");
}
formatter = "HH:mm:ss." + ms;
} else {
// 判断包含时间位数
int i = datetimeStr.indexOf(":");
i = datetimeStr.indexOf(":", i + 1);
if (i > -1) {
formatter = "HH:mm:ss";
} else {
formatter = "HH:mm";
}
}
DateTimeFormatter dateTimeFormatter = dateFormatterCache.get(formatter);
LocalTime localTime = LocalTime.parse(datetimeStr, dateTimeFormatter);
LocalDate localDate = LocalDate.of(1970, Month.JANUARY, 1);
LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime);
return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
}
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
return null;
}
}
-95
View File
@@ -1,95 +0,0 @@
<?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>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.4-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.alibaba.otter</groupId>
<artifactId>client-adapter.elasticsearch</artifactId>
<packaging>jar</packaging>
<name>canal client adapter elasticsearch module for otter ${project.version}</name>
<dependencies>
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>client-adapter.common</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba.fastsql</groupId>
<artifactId>fastsql</artifactId>
<version>2.0.0_preview_855</version>
</dependency>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>6.2.3</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>transport</artifactId>
<version>6.2.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.40</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.4</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<copy todir="${project.basedir}/../launcher/target/classes/es" overwrite="true">
<fileset dir="${project.basedir}/target/classes/es" erroronmissingdir="true">
<include name="*.yml" />
</fileset>
</copy>
</tasks>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -1,253 +0,0 @@
package com.alibaba.otter.canal.client.adapter.es;
import java.net.InetAddress;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.sql.DataSource;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.transport.client.PreBuiltTransportClient;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig;
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig.ESMapping;
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfigLoader;
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem;
import com.alibaba.otter.canal.client.adapter.es.config.SqlParser;
import com.alibaba.otter.canal.client.adapter.es.monitor.ESConfigMonitor;
import com.alibaba.otter.canal.client.adapter.es.service.ESEtlService;
import com.alibaba.otter.canal.client.adapter.es.service.ESSyncService;
import com.alibaba.otter.canal.client.adapter.es.support.ESTemplate;
import com.alibaba.otter.canal.client.adapter.support.*;
/**
* ES外部适配器
*
* @author rewerma 2018-10-20
* @version 1.0.0
*/
@SPI("es")
public class ESAdapter implements OuterAdapter {
private Map<String, ESSyncConfig> esSyncConfig = new ConcurrentHashMap<>(); // 文件名对应配置
private Map<String, Map<String, ESSyncConfig>> dbTableEsSyncConfig = new ConcurrentHashMap<>(); // schema-table对应配置
private TransportClient transportClient;
private ESSyncService esSyncService;
private ESConfigMonitor esConfigMonitor;
private Properties envProperties;
public TransportClient getTransportClient() {
return transportClient;
}
public ESSyncService getEsSyncService() {
return esSyncService;
}
public Map<String, ESSyncConfig> getEsSyncConfig() {
return esSyncConfig;
}
public Map<String, Map<String, ESSyncConfig>> getDbTableEsSyncConfig() {
return dbTableEsSyncConfig;
}
@Override
public void init(OuterAdapterConfig configuration, Properties envProperties) {
try {
this.envProperties = envProperties;
Map<String, ESSyncConfig> esSyncConfigTmp = ESSyncConfigLoader.load(envProperties);
// 过滤不匹配的key的配置
esSyncConfigTmp.forEach((key, config) -> {
if ((config.getOuterAdapterKey() == null && configuration.getKey() == null)
|| (config.getOuterAdapterKey() != null
&& config.getOuterAdapterKey().equalsIgnoreCase(configuration.getKey()))) {
esSyncConfig.put(key, config);
}
});
for (Map.Entry<String, ESSyncConfig> entry : esSyncConfig.entrySet()) {
String configName = entry.getKey();
ESSyncConfig config = entry.getValue();
SchemaItem schemaItem = SqlParser.parse(config.getEsMapping().getSql());
config.getEsMapping().setSchemaItem(schemaItem);
DruidDataSource dataSource = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
if (dataSource == null || dataSource.getUrl() == null) {
throw new RuntimeException("No data source found: " + config.getDataSourceKey());
}
Pattern pattern = Pattern.compile(".*:(.*)://.*/(.*)\\?.*$");
Matcher matcher = pattern.matcher(dataSource.getUrl());
if (!matcher.find()) {
throw new RuntimeException("Not found the schema of jdbc-url: " + config.getDataSourceKey());
}
String schema = matcher.group(2);
schemaItem.getAliasTableItems().values().forEach(tableItem -> {
Map<String, ESSyncConfig> esSyncConfigMap;
if (envProperties != null
&& !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
esSyncConfigMap = dbTableEsSyncConfig
.computeIfAbsent(StringUtils.trimToEmpty(config.getDestination()) + "-"
+ StringUtils.trimToEmpty(config.getGroupId()) + "_" + schema + "-"
+ tableItem.getTableName(),
k -> new ConcurrentHashMap<>());
} else {
esSyncConfigMap = dbTableEsSyncConfig
.computeIfAbsent(StringUtils.trimToEmpty(config.getDestination()) + "_" + schema + "-"
+ tableItem.getTableName(),
k -> new ConcurrentHashMap<>());
}
esSyncConfigMap.put(configName, config);
});
}
Map<String, String> properties = configuration.getProperties();
Settings.Builder settingBuilder = Settings.builder();
properties.forEach(settingBuilder::put);
Settings settings = settingBuilder.build();
transportClient = new PreBuiltTransportClient(settings);
String[] hostArray = configuration.getHosts().split(",");
for (String host : hostArray) {
int i = host.indexOf(":");
transportClient.addTransportAddress(new TransportAddress(InetAddress.getByName(host.substring(0, i)),
Integer.parseInt(host.substring(i + 1))));
}
ESTemplate esTemplate = new ESTemplate(transportClient);
esSyncService = new ESSyncService(esTemplate);
esConfigMonitor = new ESConfigMonitor();
esConfigMonitor.init(this, envProperties);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
@Override
public void sync(List<Dml> dmls) {
if (dmls == null || dmls.isEmpty()) {
return;
}
for (Dml dml : dmls) {
if (!dml.getIsDdl()) {
sync(dml);
}
}
esSyncService.commit(); // 批次统一提交
}
private void sync(Dml dml) {
String database = dml.getDatabase();
String table = dml.getTable();
Map<String, ESSyncConfig> configMap;
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
configMap = dbTableEsSyncConfig
.get(StringUtils.trimToEmpty(dml.getDestination()) + "-" + StringUtils.trimToEmpty(dml.getGroupId())
+ "_" + database + "-" + table);
} else {
configMap = dbTableEsSyncConfig
.get(StringUtils.trimToEmpty(dml.getDestination()) + "_" + database + "-" + table);
}
if (configMap != null && !configMap.values().isEmpty()) {
esSyncService.sync(configMap.values(), dml);
}
}
@Override
public EtlResult etl(String task, List<String> params) {
EtlResult etlResult = new EtlResult();
ESSyncConfig config = esSyncConfig.get(task);
if (config != null) {
DataSource dataSource = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
ESEtlService esEtlService = new ESEtlService(transportClient, config);
if (dataSource != null) {
return esEtlService.importData(params);
} else {
etlResult.setSucceeded(false);
etlResult.setErrorMessage("DataSource not found");
return etlResult;
}
} else {
StringBuilder resultMsg = new StringBuilder();
boolean resSuccess = true;
for (ESSyncConfig configTmp : esSyncConfig.values()) {
// 取所有的destination为task的配置
if (configTmp.getDestination().equals(task)) {
ESEtlService esEtlService = new ESEtlService(transportClient, configTmp);
EtlResult etlRes = esEtlService.importData(params);
if (!etlRes.getSucceeded()) {
resSuccess = false;
resultMsg.append(etlRes.getErrorMessage()).append("\n");
} else {
resultMsg.append(etlRes.getResultMessage()).append("\n");
}
}
}
if (resultMsg.length() > 0) {
etlResult.setSucceeded(resSuccess);
if (resSuccess) {
etlResult.setResultMessage(resultMsg.toString());
} else {
etlResult.setErrorMessage(resultMsg.toString());
}
return etlResult;
}
}
etlResult.setSucceeded(false);
etlResult.setErrorMessage("Task not found");
return etlResult;
}
@Override
public Map<String, Object> count(String task) {
ESSyncConfig config = esSyncConfig.get(task);
ESMapping mapping = config.getEsMapping();
SearchResponse response = transportClient.prepareSearch(mapping.get_index())
.setTypes(mapping.get_type())
.setSize(0)
.get();
long rowCount = response.getHits().getTotalHits();
Map<String, Object> res = new LinkedHashMap<>();
res.put("esIndex", mapping.get_index());
res.put("count", rowCount);
return res;
}
@Override
public void destroy() {
if (esConfigMonitor != null) {
esConfigMonitor.destroy();
}
if (transportClient != null) {
transportClient.close();
}
}
@Override
public String getDestination(String task) {
ESSyncConfig config = esSyncConfig.get(task);
if (config != null) {
return config.getDestination();
}
return null;
}
}
@@ -1,241 +0,0 @@
package com.alibaba.otter.canal.client.adapter.es.config;
import com.alibaba.otter.canal.client.adapter.support.AdapterConfig;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* ES 映射配置
*
* @author rewerma 2018-11-01
* @version 1.0.0
*/
public class ESSyncConfig implements AdapterConfig {
private String dataSourceKey; // 数据源key
private String outerAdapterKey; // adapter key
private String groupId; // group id
private String destination; // canal destination
private ESMapping esMapping;
public void validate() {
if (esMapping._index == null) {
throw new NullPointerException("esMapping._index");
}
if (esMapping._type == null) {
throw new NullPointerException("esMapping._type");
}
if (esMapping._id == null && esMapping.getPk() == null) {
throw new NullPointerException("esMapping._id or esMapping.pk");
}
if (esMapping.sql == null) {
throw new NullPointerException("esMapping.sql");
}
}
public String getDataSourceKey() {
return dataSourceKey;
}
public void setDataSourceKey(String dataSourceKey) {
this.dataSourceKey = dataSourceKey;
}
public String getOuterAdapterKey() {
return outerAdapterKey;
}
public void setOuterAdapterKey(String outerAdapterKey) {
this.outerAdapterKey = outerAdapterKey;
}
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public String getDestination() {
return destination;
}
public void setDestination(String destination) {
this.destination = destination;
}
public ESMapping getEsMapping() {
return esMapping;
}
public void setEsMapping(ESMapping esMapping) {
this.esMapping = esMapping;
}
public ESMapping getMapping(){
return esMapping;
}
public static class ESMapping implements AdapterMapping{
private String _index;
private String _type;
private String _id;
private boolean upsert = false;
private String pk;
private Map<String, RelationMapping> relations = new LinkedHashMap<>();
private String sql;
// 对象字段, 例: objFields:
// - _labels: array:;
private Map<String, String> objFields = new LinkedHashMap<>();
private List<String> skips = new ArrayList<>();
private int commitBatch = 1000;
private String etlCondition;
private boolean syncByTimestamp = false; // 是否按时间戳定时同步
private Long syncInterval; // 同步时间间隔
private SchemaItem schemaItem; // sql解析结果模型
public String get_index() {
return _index;
}
public void set_index(String _index) {
this._index = _index;
}
public String get_type() {
return _type;
}
public void set_type(String _type) {
this._type = _type;
}
public String get_id() {
return _id;
}
public void set_id(String _id) {
this._id = _id;
}
public boolean isUpsert() {
return upsert;
}
public void setUpsert(boolean upsert) {
this.upsert = upsert;
}
public String getPk() {
return pk;
}
public void setPk(String pk) {
this.pk = pk;
}
public Map<String, String> getObjFields() {
return objFields;
}
public void setObjFields(Map<String, String> objFields) {
this.objFields = objFields;
}
public List<String> getSkips() {
return skips;
}
public void setSkips(List<String> skips) {
this.skips = skips;
}
public Map<String, RelationMapping> getRelations() {
return relations;
}
public void setRelations(Map<String, RelationMapping> relations) {
this.relations = relations;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
public int getCommitBatch() {
return commitBatch;
}
public void setCommitBatch(int commitBatch) {
this.commitBatch = commitBatch;
}
public String getEtlCondition() {
return etlCondition;
}
public void setEtlCondition(String etlCondition) {
this.etlCondition = etlCondition;
}
public Long getSyncInterval() {
return syncInterval;
}
public void setSyncInterval(Long syncInterval) {
this.syncInterval = syncInterval;
}
public boolean isSyncByTimestamp() {
return syncByTimestamp;
}
public void setSyncByTimestamp(boolean syncByTimestamp) {
this.syncByTimestamp = syncByTimestamp;
}
public SchemaItem getSchemaItem() {
return schemaItem;
}
public void setSchemaItem(SchemaItem schemaItem) {
this.schemaItem = schemaItem;
}
}
public static class RelationMapping {
private String name;
private String parent;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getParent() {
return parent;
}
public void setParent(String parent) {
this.parent = parent;
}
}
}
@@ -1,45 +0,0 @@
package com.alibaba.otter.canal.client.adapter.es.config;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.client.adapter.config.YmlConfigBinder;
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
/**
* ES 配置装载器
*
* @author rewerma 2018-11-01
* @version 1.0.0
*/
public class ESSyncConfigLoader {
private static Logger logger = LoggerFactory.getLogger(ESSyncConfigLoader.class);
public static synchronized Map<String, ESSyncConfig> load(Properties envProperties) {
logger.info("## Start loading es mapping config ... ");
Map<String, ESSyncConfig> esSyncConfig = new LinkedHashMap<>();
Map<String, String> configContentMap = MappingConfigsLoader.loadConfigs("es");
configContentMap.forEach((fileName, content) -> {
ESSyncConfig config = YmlConfigBinder.bindYmlToObj(null, content, ESSyncConfig.class, null, envProperties);
if (config == null) {
return;
}
try {
config.validate();
} catch (Exception e) {
throw new RuntimeException("ERROR Config: " + fileName + " " + e.getMessage(), e);
}
esSyncConfig.put(fileName, config);
});
logger.info("## ES mapping config loaded");
return esSyncConfig;
}
}
@@ -1,437 +0,0 @@
package com.alibaba.otter.canal.client.adapter.es.config;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig.ESMapping;
/**
* ES 映射配置视图
*
* @author rewerma 2018-11-01
* @version 1.0.0
*/
public class SchemaItem {
private Map<String, TableItem> aliasTableItems = new LinkedHashMap<>(); // 别名对应表名
private Map<String, FieldItem> selectFields = new LinkedHashMap<>(); // 查询字段
private String sql;
private volatile Map<String, List<TableItem>> tableItemAliases;
private volatile Map<String, List<FieldItem>> columnFields;
private volatile Boolean allFieldsSimple;
public void init() {
this.getTableItemAliases();
this.getColumnFields();
this.isAllFieldsSimple();
aliasTableItems.values().forEach(tableItem -> {
tableItem.getRelationTableFields();
tableItem.getRelationSelectFieldItems();
});
}
public Map<String, TableItem> getAliasTableItems() {
return aliasTableItems;
}
public void setAliasTableItems(Map<String, TableItem> aliasTableItems) {
this.aliasTableItems = aliasTableItems;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
public Map<String, FieldItem> getSelectFields() {
return selectFields;
}
public void setSelectFields(Map<String, FieldItem> selectFields) {
this.selectFields = selectFields;
}
public String toSql() {
// todo
return null;
}
public Map<String, List<TableItem>> getTableItemAliases() {
if (tableItemAliases == null) {
synchronized (SchemaItem.class) {
if (tableItemAliases == null) {
tableItemAliases = new LinkedHashMap<>();
aliasTableItems.forEach((alias, tableItem) -> {
List<TableItem> aliases = tableItemAliases
.computeIfAbsent(tableItem.getTableName().toLowerCase(), k -> new ArrayList<>());
aliases.add(tableItem);
});
}
}
}
return tableItemAliases;
}
public Map<String, List<FieldItem>> getColumnFields() {
if (columnFields == null) {
synchronized (SchemaItem.class) {
if (columnFields == null) {
columnFields = new LinkedHashMap<>();
getSelectFields()
.forEach((fieldName, fieldItem) -> fieldItem.getColumnItems().forEach(columnItem -> {
// TableItem tableItem = getAliasTableItems().get(columnItem.getOwner());
// if (!tableItem.isSubQuery()) {
List<FieldItem> fieldItems = columnFields.computeIfAbsent(
columnItem.getOwner() + "." + columnItem.getColumnName(),
k -> new ArrayList<>());
fieldItems.add(fieldItem);
// } else {
// tableItem.getSubQueryFields().forEach(subQueryField -> {
// List<FieldItem> fieldItems = columnFields.computeIfAbsent(
// columnItem.getOwner() + "." + subQueryField.getColumn().getColumnName(),
// k -> new ArrayList<>());
// fieldItems.add(fieldItem);
// });
// }
}));
}
}
}
return columnFields;
}
public boolean isAllFieldsSimple() {
if (allFieldsSimple == null) {
synchronized (SchemaItem.class) {
if (allFieldsSimple == null) {
allFieldsSimple = true;
for (FieldItem fieldItem : getSelectFields().values()) {
if (fieldItem.isMethod() || fieldItem.isBinaryOp()) {
allFieldsSimple = false;
break;
}
}
}
}
}
return allFieldsSimple;
}
public TableItem getMainTable() {
if (!aliasTableItems.isEmpty()) {
return aliasTableItems.values().iterator().next();
} else {
return null;
}
}
public FieldItem getIdFieldItem(ESMapping mapping) {
if (mapping.get_id() != null) {
return getSelectFields().get(mapping.get_id());
} else {
return getSelectFields().get(mapping.getPk());
}
}
public static class TableItem {
private SchemaItem schemaItem;
private String schema;
private String tableName;
private String alias;
private String subQuerySql;
private List<FieldItem> subQueryFields = new ArrayList<>();
private List<RelationFieldsPair> relationFields = new ArrayList<>();
private boolean main;
private boolean subQuery;
private volatile Map<FieldItem, List<FieldItem>> relationTableFields; // 当前表关联条件字段对应主表查询字段
private volatile List<FieldItem> relationSelectFieldItems; // 子表所在主表的查询字段
public TableItem(SchemaItem schemaItem){
this.schemaItem = schemaItem;
}
public SchemaItem getSchemaItem() {
return schemaItem;
}
public void setSchemaItem(SchemaItem schemaItem) {
this.schemaItem = schemaItem;
}
public String getSchema() {
return schema;
}
public void setSchema(String schema) {
this.schema = schema;
}
public String getTableName() {
return tableName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
public String getSubQuerySql() {
return subQuerySql;
}
public void setSubQuerySql(String subQuerySql) {
this.subQuerySql = subQuerySql;
}
public boolean isMain() {
return main;
}
public void setMain(boolean main) {
this.main = main;
}
public boolean isSubQuery() {
return subQuery;
}
public void setSubQuery(boolean subQuery) {
this.subQuery = subQuery;
}
public List<FieldItem> getSubQueryFields() {
return subQueryFields;
}
public void setSubQueryFields(List<FieldItem> subQueryFields) {
this.subQueryFields = subQueryFields;
}
public List<RelationFieldsPair> getRelationFields() {
return relationFields;
}
public void setRelationFields(List<RelationFieldsPair> relationFields) {
this.relationFields = relationFields;
}
public Map<FieldItem, List<FieldItem>> getRelationTableFields() {
if (relationTableFields == null) {
synchronized (SchemaItem.class) {
if (relationTableFields == null) {
relationTableFields = new LinkedHashMap<>();
getRelationFields().forEach(relationFieldsPair -> {
FieldItem leftFieldItem = relationFieldsPair.getLeftFieldItem();
FieldItem rightFieldItem = relationFieldsPair.getRightFieldItem();
FieldItem currentTableRelField = null;
if (getAlias().equals(leftFieldItem.getOwner())) {
currentTableRelField = leftFieldItem;
} else if (getAlias().equals(rightFieldItem.getOwner())) {
currentTableRelField = rightFieldItem;
}
if (currentTableRelField != null) {
List<FieldItem> selectFieldItem = getSchemaItem().getColumnFields()
.get(leftFieldItem.getOwner() + "." + leftFieldItem.getColumn().getColumnName());
if (selectFieldItem != null && !selectFieldItem.isEmpty()) {
relationTableFields.put(currentTableRelField, selectFieldItem);
} else {
selectFieldItem = getSchemaItem().getColumnFields()
.get(rightFieldItem.getOwner() + "."
+ rightFieldItem.getColumn().getColumnName());
if (selectFieldItem != null && !selectFieldItem.isEmpty()) {
relationTableFields.put(currentTableRelField, selectFieldItem);
} else {
throw new UnsupportedOperationException(
"Relation condition column must in select columns.");
}
}
}
});
}
}
}
return relationTableFields;
}
public List<FieldItem> getRelationSelectFieldItems() {
if (relationSelectFieldItems == null) {
synchronized (SchemaItem.class) {
if (relationSelectFieldItems == null) {
List<FieldItem> relationSelectFieldItemsTmp = new ArrayList<>();
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
if (fieldItem.getOwners().contains(getAlias())) {
relationSelectFieldItemsTmp.add(fieldItem);
}
}
relationSelectFieldItems = relationSelectFieldItemsTmp;
}
}
}
return relationSelectFieldItems;
}
}
public static class RelationFieldsPair {
private FieldItem leftFieldItem;
private FieldItem rightFieldItem;
public RelationFieldsPair(FieldItem leftFieldItem, FieldItem rightFieldItem){
this.leftFieldItem = leftFieldItem;
this.rightFieldItem = rightFieldItem;
}
public FieldItem getLeftFieldItem() {
return leftFieldItem;
}
public void setLeftFieldItem(FieldItem leftFieldItem) {
this.leftFieldItem = leftFieldItem;
}
public FieldItem getRightFieldItem() {
return rightFieldItem;
}
public void setRightFieldItem(FieldItem rightFieldItem) {
this.rightFieldItem = rightFieldItem;
}
}
public static class FieldItem {
private String fieldName;
private String expr;
private List<ColumnItem> columnItems = new ArrayList<>();
private List<String> owners = new ArrayList<>();
private boolean method;
private boolean binaryOp;
public String getFieldName() {
return fieldName;
}
public void setFieldName(String fieldName) {
this.fieldName = fieldName;
}
public String getExpr() {
return expr;
}
public void setExpr(String expr) {
this.expr = expr;
}
public List<ColumnItem> getColumnItems() {
return columnItems;
}
public void setColumnItems(List<ColumnItem> columnItems) {
this.columnItems = columnItems;
}
public boolean isMethod() {
return method;
}
public void setMethod(boolean method) {
this.method = method;
}
public boolean isBinaryOp() {
return binaryOp;
}
public void setBinaryOp(boolean binaryOp) {
this.binaryOp = binaryOp;
}
public List<String> getOwners() {
return owners;
}
public void setOwners(List<String> owners) {
this.owners = owners;
}
public void addColumn(ColumnItem columnItem) {
columnItems.add(columnItem);
}
public ColumnItem getColumn() {
if (!columnItems.isEmpty()) {
return columnItems.get(0);
} else {
return null;
}
}
public String getOwner() {
if (!owners.isEmpty()) {
return owners.get(0);
} else {
return null;
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FieldItem fieldItem = (FieldItem) o;
return fieldName != null ? fieldName.equals(fieldItem.fieldName) : fieldItem.fieldName == null;
}
@Override
public int hashCode() {
return fieldName != null ? fieldName.hashCode() : 0;
}
}
public static class ColumnItem {
private String owner;
private String columnName;
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
public String getColumnName() {
return columnName;
}
public void setColumnName(String columnName) {
this.columnName = columnName;
}
}
}
@@ -1,235 +0,0 @@
package com.alibaba.otter.canal.client.adapter.es.config;
import static com.alibaba.fastsql.sql.ast.expr.SQLBinaryOperator.BooleanAnd;
import static com.alibaba.fastsql.sql.ast.expr.SQLBinaryOperator.Equality;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import com.alibaba.fastsql.sql.SQLUtils;
import com.alibaba.fastsql.sql.ast.SQLExpr;
import com.alibaba.fastsql.sql.ast.expr.*;
import com.alibaba.fastsql.sql.ast.statement.SQLExprTableSource;
import com.alibaba.fastsql.sql.ast.statement.SQLJoinTableSource;
import com.alibaba.fastsql.sql.ast.statement.SQLSelectStatement;
import com.alibaba.fastsql.sql.ast.statement.SQLSubqueryTableSource;
import com.alibaba.fastsql.sql.ast.statement.SQLTableSource;
import com.alibaba.fastsql.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock;
import com.alibaba.fastsql.sql.dialect.mysql.parser.MySqlStatementParser;
import com.alibaba.fastsql.sql.parser.ParserException;
import com.alibaba.fastsql.sql.parser.SQLStatementParser;
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.ColumnItem;
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.FieldItem;
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.RelationFieldsPair;
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.TableItem;
/**
* ES同步指定sql格式解析
*
* @author rewerma 2018-10-26 下午03:45:49
* @version 1.0.0
*/
public class SqlParser {
/**
* 解析sql
*
* @param sql sql
* @return 视图对象
*/
public static SchemaItem parse(String sql) {
try {
SQLStatementParser parser = new MySqlStatementParser(sql);
SQLSelectStatement statement = (SQLSelectStatement) parser.parseStatement();
MySqlSelectQueryBlock sqlSelectQueryBlock = (MySqlSelectQueryBlock) statement.getSelect().getQuery();
SchemaItem schemaItem = new SchemaItem();
schemaItem.setSql(SQLUtils.toMySqlString(sqlSelectQueryBlock));
SQLTableSource sqlTableSource = sqlSelectQueryBlock.getFrom();
List<TableItem> tableItems = new ArrayList<>();
SqlParser.visitSelectTable(schemaItem, sqlTableSource, tableItems, null);
tableItems.forEach(tableItem -> schemaItem.getAliasTableItems().put(tableItem.getAlias(), tableItem));
List<FieldItem> fieldItems = collectSelectQueryFields(sqlSelectQueryBlock);
fieldItems.forEach(fieldItem -> schemaItem.getSelectFields().put(fieldItem.getFieldName(), fieldItem));
schemaItem.init();
if (schemaItem.getAliasTableItems().isEmpty() || schemaItem.getSelectFields().isEmpty()) {
throw new ParserException("Parse sql error");
}
return schemaItem;
} catch (Exception e) {
throw new ParserException();
}
}
/**
* 归集字段
*
* @param sqlSelectQueryBlock sqlSelectQueryBlock
* @return 字段属性列表
*/
private static List<FieldItem> collectSelectQueryFields(MySqlSelectQueryBlock sqlSelectQueryBlock) {
return sqlSelectQueryBlock.getSelectList().stream().map(selectItem -> {
FieldItem fieldItem = new FieldItem();
fieldItem.setFieldName(selectItem.getAlias());
fieldItem.setExpr(selectItem.toString());
visitColumn(selectItem.getExpr(), fieldItem);
return fieldItem;
}).collect(Collectors.toList());
}
/**
* 解析字段
*
* @param expr sql expr
* @param fieldItem 字段属性
*/
private static void visitColumn(SQLExpr expr, FieldItem fieldItem) {
if (expr instanceof SQLIdentifierExpr) {
// 无owner
SQLIdentifierExpr identifierExpr = (SQLIdentifierExpr) expr;
if (fieldItem.getFieldName() == null) {
fieldItem.setFieldName(identifierExpr.getName());
fieldItem.setExpr(identifierExpr.toString());
}
ColumnItem columnItem = new ColumnItem();
columnItem.setColumnName(identifierExpr.getName());
fieldItem.getOwners().add(null);
fieldItem.addColumn(columnItem);
} else if (expr instanceof SQLPropertyExpr) {
// 有owner
SQLPropertyExpr sqlPropertyExpr = (SQLPropertyExpr) expr;
if (fieldItem.getFieldName() == null) {
fieldItem.setFieldName(sqlPropertyExpr.getName());
fieldItem.setExpr(sqlPropertyExpr.toString());
}
fieldItem.getOwners().add(sqlPropertyExpr.getOwnernName());
ColumnItem columnItem = new ColumnItem();
columnItem.setColumnName(sqlPropertyExpr.getName());
columnItem.setOwner(sqlPropertyExpr.getOwnernName());
fieldItem.addColumn(columnItem);
} else if (expr instanceof SQLMethodInvokeExpr) {
SQLMethodInvokeExpr methodInvokeExpr = (SQLMethodInvokeExpr) expr;
fieldItem.setMethod(true);
for (SQLExpr sqlExpr : methodInvokeExpr.getArguments()) {
visitColumn(sqlExpr, fieldItem);
}
} else if (expr instanceof SQLBinaryOpExpr) {
SQLBinaryOpExpr sqlBinaryOpExpr = (SQLBinaryOpExpr) expr;
fieldItem.setBinaryOp(true);
visitColumn(sqlBinaryOpExpr.getLeft(), fieldItem);
visitColumn(sqlBinaryOpExpr.getRight(), fieldItem);
} else if (expr instanceof SQLCaseExpr) {
SQLCaseExpr sqlCaseExpr = (SQLCaseExpr) expr;
fieldItem.setMethod(true);
sqlCaseExpr.getItems().forEach(item-> visitColumn(item.getConditionExpr(), fieldItem));
}else if(expr instanceof SQLCharExpr) {
SQLCharExpr sqlCharExpr = (SQLCharExpr) expr;
String owner = null;
String columnName = null;
if (sqlCharExpr.getParent() instanceof SQLCaseExpr.Item) {
owner = ((SQLPropertyExpr) ((SQLCaseExpr.Item) sqlCharExpr.getParent()).getValueExpr()).getOwnernName();
columnName = ((SQLPropertyExpr) ((SQLCaseExpr.Item) sqlCharExpr.getParent()).getValueExpr()).getName();
}
if (fieldItem.getFieldName() == null) {
fieldItem.setFieldName(columnName);
fieldItem.setExpr(sqlCharExpr.toString());
}
ColumnItem columnItem = new ColumnItem();
columnItem.setColumnName(columnName);
columnItem.setOwner(owner);
fieldItem.getOwners().add(owner);
fieldItem.addColumn(columnItem);
}
}
/**
* 解析表
*
* @param schemaItem 视图对象
* @param sqlTableSource sqlTableSource
* @param tableItems 表对象列表
* @param tableItemTmp 表对象(临时)
*/
private static void visitSelectTable(SchemaItem schemaItem, SQLTableSource sqlTableSource,
List<TableItem> tableItems, TableItem tableItemTmp) {
if (sqlTableSource instanceof SQLExprTableSource) {
SQLExprTableSource sqlExprTableSource = (SQLExprTableSource) sqlTableSource;
TableItem tableItem;
if (tableItemTmp != null) {
tableItem = tableItemTmp;
} else {
tableItem = new TableItem(schemaItem);
}
tableItem.setSchema(sqlExprTableSource.getSchema());
tableItem.setTableName(sqlExprTableSource.getTableName());
if (tableItem.getAlias() == null) {
tableItem.setAlias(sqlExprTableSource.getAlias());
}
if (tableItems.isEmpty()) {
// 第一张表为主表
tableItem.setMain(true);
}
tableItems.add(tableItem);
} else if (sqlTableSource instanceof SQLJoinTableSource) {
SQLJoinTableSource sqlJoinTableSource = (SQLJoinTableSource) sqlTableSource;
SQLTableSource leftTableSource = sqlJoinTableSource.getLeft();
visitSelectTable(schemaItem, leftTableSource, tableItems, null);
SQLTableSource rightTableSource = sqlJoinTableSource.getRight();
TableItem rightTableItem = new TableItem(schemaItem);
// 解析on条件字段
visitOnCondition(sqlJoinTableSource.getCondition(), rightTableItem);
visitSelectTable(schemaItem, rightTableSource, tableItems, rightTableItem);
} else if (sqlTableSource instanceof SQLSubqueryTableSource) {
SQLSubqueryTableSource subQueryTableSource = (SQLSubqueryTableSource) sqlTableSource;
MySqlSelectQueryBlock sqlSelectQuery = (MySqlSelectQueryBlock) subQueryTableSource.getSelect().getQuery();
TableItem tableItem;
if (tableItemTmp != null) {
tableItem = tableItemTmp;
} else {
tableItem = new TableItem(schemaItem);
}
tableItem.setAlias(subQueryTableSource.getAlias());
tableItem.setSubQuerySql(SQLUtils.toMySqlString(sqlSelectQuery));
tableItem.setSubQuery(true);
tableItem.setSubQueryFields(collectSelectQueryFields(sqlSelectQuery));
visitSelectTable(schemaItem, sqlSelectQuery.getFrom(), tableItems, tableItem);
}
}
/**
* 解析on条件
*
* @param expr sql expr
* @param tableItem 表对象
*/
private static void visitOnCondition(SQLExpr expr, TableItem tableItem) {
if (!(expr instanceof SQLBinaryOpExpr)) {
throw new UnsupportedOperationException();
}
SQLBinaryOpExpr sqlBinaryOpExpr = (SQLBinaryOpExpr) expr;
if (sqlBinaryOpExpr.getOperator() == BooleanAnd) {
visitOnCondition(sqlBinaryOpExpr.getLeft(), tableItem);
visitOnCondition(sqlBinaryOpExpr.getRight(), tableItem);
} else if (sqlBinaryOpExpr.getOperator() == Equality) {
FieldItem leftFieldItem = new FieldItem();
visitColumn(sqlBinaryOpExpr.getLeft(), leftFieldItem);
if (leftFieldItem.getColumnItems().size() != 1 || leftFieldItem.isMethod() || leftFieldItem.isBinaryOp()) {
throw new UnsupportedOperationException("Unsupported for complex of on-condition");
}
FieldItem rightFieldItem = new FieldItem();
visitColumn(sqlBinaryOpExpr.getRight(), rightFieldItem);
if (rightFieldItem.getColumnItems().size() != 1 || rightFieldItem.isMethod()
|| rightFieldItem.isBinaryOp()) {
throw new UnsupportedOperationException("Unsupported for complex of on-condition");
}
tableItem.getRelationFields().add(new RelationFieldsPair(leftFieldItem, rightFieldItem));
} else {
throw new UnsupportedOperationException("Unsupported for complex of on-condition");
}
}
}
@@ -1,165 +0,0 @@
package com.alibaba.otter.canal.client.adapter.es.monitor;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.otter.canal.client.adapter.config.YmlConfigBinder;
import com.alibaba.otter.canal.client.adapter.es.ESAdapter;
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig;
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem;
import com.alibaba.otter.canal.client.adapter.es.config.SqlParser;
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
import com.alibaba.otter.canal.client.adapter.support.Util;
public class ESConfigMonitor {
private static final Logger logger = LoggerFactory.getLogger(ESConfigMonitor.class);
private static final String adapterName = "es";
private ESAdapter esAdapter;
private Properties envProperties;
private FileAlterationMonitor fileMonitor;
public void init(ESAdapter esAdapter, Properties envProperties) {
this.esAdapter = esAdapter;
this.envProperties = envProperties;
File confDir = Util.getConfDirPath(adapterName);
try {
FileAlterationObserver observer = new FileAlterationObserver(confDir,
FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml")));
FileListener listener = new FileListener();
observer.addListener(listener);
fileMonitor = new FileAlterationMonitor(3000, observer);
fileMonitor.start();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
public void destroy() {
try {
fileMonitor.stop();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
private class FileListener extends FileAlterationListenerAdaptor {
@Override
public void onFileCreate(File file) {
super.onFileCreate(file);
try {
// 加载新增的配置文件
String configContent = MappingConfigsLoader.loadConfig(adapterName + File.separator + file.getName());
ESSyncConfig config = YmlConfigBinder
.bindYmlToObj(null, configContent, ESSyncConfig.class, null, envProperties);
if (config != null) {
config.validate();
addConfigToCache(file, config);
logger.info("Add a new es mapping config: {} to canal adapter", file.getName());
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
@Override
public void onFileChange(File file) {
super.onFileChange(file);
try {
if (esAdapter.getEsSyncConfig().containsKey(file.getName())) {
// 加载配置文件
String configContent = MappingConfigsLoader
.loadConfig(adapterName + File.separator + file.getName());
if (configContent == null) {
onFileDelete(file);
return;
}
ESSyncConfig config = YmlConfigBinder
.bindYmlToObj(null, configContent, ESSyncConfig.class, null, envProperties);
if (config == null) {
return;
}
config.validate();
if (esAdapter.getEsSyncConfig().containsKey(file.getName())) {
deleteConfigFromCache(file);
}
addConfigToCache(file, config);
logger.info("Change a es mapping config: {} of canal adapter", file.getName());
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
@Override
public void onFileDelete(File file) {
super.onFileDelete(file);
try {
if (esAdapter.getEsSyncConfig().containsKey(file.getName())) {
deleteConfigFromCache(file);
logger.info("Delete a es mapping config: {} of canal adapter", file.getName());
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
private void addConfigToCache(File file, ESSyncConfig config) {
esAdapter.getEsSyncConfig().put(file.getName(), config);
SchemaItem schemaItem = SqlParser.parse(config.getEsMapping().getSql());
config.getEsMapping().setSchemaItem(schemaItem);
DruidDataSource dataSource = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
if (dataSource == null || dataSource.getUrl() == null) {
throw new RuntimeException("No data source found: " + config.getDataSourceKey());
}
Pattern pattern = Pattern.compile(".*:(.*)://.*/(.*)\\?.*$");
Matcher matcher = pattern.matcher(dataSource.getUrl());
if (!matcher.find()) {
throw new RuntimeException("Not found the schema of jdbc-url: " + config.getDataSourceKey());
}
String schema = matcher.group(2);
schemaItem.getAliasTableItems().values().forEach(tableItem -> {
Map<String, ESSyncConfig> esSyncConfigMap = esAdapter.getDbTableEsSyncConfig()
.computeIfAbsent(schema + "-" + tableItem.getTableName(), k -> new HashMap<>());
esSyncConfigMap.put(file.getName(), config);
});
}
private void deleteConfigFromCache(File file) {
esAdapter.getEsSyncConfig().remove(file.getName());
for (Map<String, ESSyncConfig> configMap : esAdapter.getDbTableEsSyncConfig().values()) {
if (configMap != null) {
configMap.remove(file.getName());
}
}
}
}
}
@@ -1,216 +0,0 @@
package com.alibaba.otter.canal.client.adapter.es.service;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import javax.sql.DataSource;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequestBuilder;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchHit;
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig;
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig.ESMapping;
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.FieldItem;
import com.alibaba.otter.canal.client.adapter.es.support.ESTemplate;
import com.alibaba.otter.canal.client.adapter.support.AbstractEtlService;
import com.alibaba.otter.canal.client.adapter.support.AdapterConfig;
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
import com.alibaba.otter.canal.client.adapter.support.Util;
/**
* ES ETL Service
*
* @author rewerma 2018-11-01
* @version 1.0.0
*/
public class ESEtlService extends AbstractEtlService {
private TransportClient transportClient;
private ESTemplate esTemplate;
private ESSyncConfig config;
public ESEtlService(TransportClient transportClient, ESSyncConfig config){
super("ES", config);
this.transportClient = transportClient;
this.esTemplate = new ESTemplate(transportClient);
this.config = config;
}
public EtlResult importData(List<String> params) {
ESMapping mapping = config.getEsMapping();
logger.info("start etl to import data to index: {}", mapping.get_index());
String sql = mapping.getSql();
return importData(sql, params);
}
private void processFailBulkResponse(BulkResponse bulkResponse) {
for (BulkItemResponse response : bulkResponse.getItems()) {
if (!response.isFailed()) {
continue;
}
if (response.getFailure().getStatus() == RestStatus.NOT_FOUND) {
logger.warn(response.getFailureMessage());
} else {
logger.error("全量导入数据有误 {}", response.getFailureMessage());
throw new RuntimeException("全量数据 etl 异常: " + response.getFailureMessage());
}
}
}
protected boolean executeSqlImport(DataSource ds, String sql, List<Object> values,
AdapterConfig.AdapterMapping adapterMapping, AtomicLong impCount,
List<String> errMsg) {
try {
ESMapping mapping = (ESMapping) adapterMapping;
Util.sqlRS(ds, sql, values, rs -> {
int count = 0;
try {
BulkRequestBuilder bulkRequestBuilder = transportClient.prepareBulk();
long batchBegin = System.currentTimeMillis();
while (rs.next()) {
Map<String, Object> esFieldData = new LinkedHashMap<>();
Object idVal = null;
for (FieldItem fieldItem : mapping.getSchemaItem().getSelectFields().values()) {
String fieldName = fieldItem.getFieldName();
if (mapping.getSkips().contains(fieldName)) {
continue;
}
// 如果是主键字段则不插入
if (fieldItem.getFieldName().equals(mapping.get_id())) {
idVal = esTemplate.getValFromRS(mapping, rs, fieldName, fieldName);
} else {
Object val = esTemplate.getValFromRS(mapping, rs, fieldName, fieldName);
esFieldData.put(Util.cleanColumn(fieldName), val);
}
}
if (!mapping.getRelations().isEmpty()) {
mapping.getRelations().forEach((relationField, relationMapping) -> {
Map<String, Object> relations = new HashMap<>();
relations.put("name", relationMapping.getName());
if (StringUtils.isNotEmpty(relationMapping.getParent())) {
FieldItem parentFieldItem = mapping.getSchemaItem()
.getSelectFields()
.get(relationMapping.getParent());
Object parentVal;
try {
parentVal = esTemplate.getValFromRS(mapping,
rs,
parentFieldItem.getFieldName(),
parentFieldItem.getFieldName());
} catch (SQLException e) {
throw new RuntimeException(e);
}
if (parentVal != null) {
relations.put("parent", parentVal.toString());
esFieldData.put("$parent_routing", parentVal.toString());
}
}
esFieldData.put(Util.cleanColumn(relationField), relations);
});
}
if (idVal != null) {
String parentVal = (String) esFieldData.remove("$parent_routing");
if (mapping.isUpsert()) {
UpdateRequestBuilder updateRequestBuilder = transportClient
.prepareUpdate(mapping.get_index(), mapping.get_type(), idVal.toString())
.setDoc(esFieldData)
.setDocAsUpsert(true);
if (StringUtils.isNotEmpty(parentVal)) {
updateRequestBuilder.setRouting(parentVal);
}
bulkRequestBuilder.add(updateRequestBuilder);
} else {
IndexRequestBuilder indexRequestBuilder = transportClient
.prepareIndex(mapping.get_index(), mapping.get_type(), idVal.toString())
.setSource(esFieldData);
if (StringUtils.isNotEmpty(parentVal)) {
indexRequestBuilder.setRouting(parentVal);
}
bulkRequestBuilder.add(indexRequestBuilder);
}
} else {
idVal = esFieldData.get(mapping.getPk());
SearchResponse response = transportClient.prepareSearch(mapping.get_index())
.setTypes(mapping.get_type())
.setQuery(QueryBuilders.termQuery(mapping.getPk(), idVal))
.setSize(10000)
.get();
for (SearchHit hit : response.getHits()) {
bulkRequestBuilder.add(
transportClient.prepareUpdate(mapping.get_index(), mapping.get_type(), hit.getId())
.setDoc(esFieldData));
}
}
if (bulkRequestBuilder.numberOfActions() % mapping.getCommitBatch() == 0
&& bulkRequestBuilder.numberOfActions() > 0) {
long esBatchBegin = System.currentTimeMillis();
BulkResponse rp = bulkRequestBuilder.execute().actionGet();
if (rp.hasFailures()) {
this.processFailBulkResponse(rp);
}
if (logger.isTraceEnabled()) {
logger.trace("全量数据批量导入批次耗时: {}, es执行时间: {}, 批次大小: {}, index; {}",
(System.currentTimeMillis() - batchBegin),
(System.currentTimeMillis() - esBatchBegin),
bulkRequestBuilder.numberOfActions(),
mapping.get_index());
}
batchBegin = System.currentTimeMillis();
bulkRequestBuilder = transportClient.prepareBulk();
}
count++;
impCount.incrementAndGet();
}
if (bulkRequestBuilder.numberOfActions() > 0) {
long esBatchBegin = System.currentTimeMillis();
BulkResponse rp = bulkRequestBuilder.execute().actionGet();
if (rp.hasFailures()) {
this.processFailBulkResponse(rp);
}
if (logger.isTraceEnabled()) {
logger.trace("全量数据批量导入最后批次耗时: {}, es执行时间: {}, 批次大小: {}, index; {}",
(System.currentTimeMillis() - batchBegin),
(System.currentTimeMillis() - esBatchBegin),
bulkRequestBuilder.numberOfActions(),
mapping.get_index());
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
errMsg.add(mapping.get_index() + " etl failed! ==>" + e.getMessage());
throw new RuntimeException(e);
}
return count;
});
return true;
} catch (Exception e) {
logger.error(e.getMessage(), e);
return false;
}
}
}
@@ -1,854 +0,0 @@
package com.alibaba.otter.canal.client.adapter.es.service;
import java.util.*;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig;
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig.ESMapping;
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem;
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.ColumnItem;
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.FieldItem;
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.TableItem;
import com.alibaba.otter.canal.client.adapter.es.support.ESSyncUtil;
import com.alibaba.otter.canal.client.adapter.es.support.ESTemplate;
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
import com.alibaba.otter.canal.client.adapter.support.Dml;
import com.alibaba.otter.canal.client.adapter.support.Util;
/**
* ES 同步 Service
*
* @author rewerma 2018-11-01
* @version 1.0.0
*/
public class ESSyncService {
private static Logger logger = LoggerFactory.getLogger(ESSyncService.class);
private ESTemplate esTemplate;
public ESSyncService(ESTemplate esTemplate){
this.esTemplate = esTemplate;
}
public void sync(Collection<ESSyncConfig> esSyncConfigs, Dml dml) {
long begin = System.currentTimeMillis();
if (esSyncConfigs != null) {
if (logger.isTraceEnabled()) {
logger.trace("Destination: {}, database:{}, table:{}, type:{}, affected index count: {}",
dml.getDestination(),
dml.getDatabase(),
dml.getTable(),
dml.getType(),
esSyncConfigs.size());
}
for (ESSyncConfig config : esSyncConfigs) {
if (logger.isTraceEnabled()) {
logger.trace("Prepared to sync index: {}, destination: {}",
config.getEsMapping().get_index(),
dml.getDestination());
}
this.sync(config, dml);
if (logger.isTraceEnabled()) {
logger.trace("Sync completed: {}, destination: {}",
config.getEsMapping().get_index(),
dml.getDestination());
}
}
if (logger.isTraceEnabled()) {
logger.trace("Sync elapsed time: {} ms, affected indexes count{}, destination: {}",
(System.currentTimeMillis() - begin),
esSyncConfigs.size(),
dml.getDestination());
}
if (logger.isDebugEnabled()) {
StringBuilder configIndexes = new StringBuilder();
esSyncConfigs
.forEach(esSyncConfig -> configIndexes.append(esSyncConfig.getEsMapping().get_index()).append(" "));
logger.debug("DML: {} \nAffected indexes: {}",
JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue),
configIndexes.toString());
}
}
}
public void sync(ESSyncConfig config, Dml dml) {
try {
// 如果是按时间戳定时更新则返回
if (config.getEsMapping().isSyncByTimestamp()) {
return;
}
long begin = System.currentTimeMillis();
String type = dml.getType();
if (type != null && type.equalsIgnoreCase("INSERT")) {
insert(config, dml);
} else if (type != null && type.equalsIgnoreCase("UPDATE")) {
update(config, dml);
} else if (type != null && type.equalsIgnoreCase("DELETE")) {
delete(config, dml);
} else {
return;
}
if (logger.isTraceEnabled()) {
logger.trace("Sync elapsed time: {} ms,destination: {}, es index: {}",
(System.currentTimeMillis() - begin),
dml.getDestination(),
config.getEsMapping().get_index());
}
} catch (Throwable e) {
logger.error("sync error, es index: {}, DML : {}", config.getEsMapping().get_index(), dml);
throw new RuntimeException(e);
}
}
/**
* 插入操作dml
*
* @param config es配置
* @param dml dml数据
*/
private void insert(ESSyncConfig config, Dml dml) {
List<Map<String, Object>> dataList = dml.getData();
if (dataList == null || dataList.isEmpty()) {
return;
}
SchemaItem schemaItem = config.getEsMapping().getSchemaItem();
for (Map<String, Object> data : dataList) {
if (data == null || data.isEmpty()) {
continue;
}
if (schemaItem.getAliasTableItems().size() == 1 && schemaItem.isAllFieldsSimple()) {
// ------单表 & 所有字段都为简单字段------
singleTableSimpleFiledInsert(config, dml, data);
} else {
// ------是主表 查询sql来插入------
if (schemaItem.getMainTable().getTableName().equalsIgnoreCase(dml.getTable())) {
mainTableInsert(config, dml, data);
}
// 从表的操作
for (TableItem tableItem : schemaItem.getAliasTableItems().values()) {
if (tableItem.isMain()) {
continue;
}
if (!tableItem.getTableName().equals(dml.getTable())) {
continue;
}
// 关联条件出现在主表查询条件是否为简单字段
boolean allFieldsSimple = true;
for (FieldItem fieldItem : tableItem.getRelationSelectFieldItems()) {
if (fieldItem.isMethod() || fieldItem.isBinaryOp()) {
allFieldsSimple = false;
break;
}
}
// 所有查询字段均为简单字段
if (allFieldsSimple) {
// 不是子查询
if (!tableItem.isSubQuery()) {
// ------关联表简单字段插入------
Map<String, Object> esFieldData = new LinkedHashMap<>();
for (FieldItem fieldItem : tableItem.getRelationSelectFieldItems()) {
Object value = esTemplate.getValFromData(config.getEsMapping(),
data,
fieldItem.getFieldName(),
fieldItem.getColumn().getColumnName());
esFieldData.put(Util.cleanColumn(fieldItem.getFieldName()), value);
}
joinTableSimpleFieldOperation(config, dml, data, tableItem, esFieldData);
} else {
// ------关联子表简单字段插入------
subTableSimpleFieldOperation(config, dml, data, null, tableItem);
}
} else {
// ------关联子表复杂字段插入 执行全sql更新es------
wholeSqlOperation(config, dml, data, null, tableItem);
}
}
}
}
}
/**
* 更新操作dml
*
* @param config es配置
* @param dml dml数据
*/
private void update(ESSyncConfig config, Dml dml) {
List<Map<String, Object>> dataList = dml.getData();
List<Map<String, Object>> oldList = dml.getOld();
if (dataList == null || dataList.isEmpty() || oldList == null || oldList.isEmpty()) {
return;
}
SchemaItem schemaItem = config.getEsMapping().getSchemaItem();
int i = 0;
for (Map<String, Object> data : dataList) {
Map<String, Object> old = oldList.get(i);
if (data == null || data.isEmpty() || old == null || old.isEmpty()) {
continue;
}
if (schemaItem.getAliasTableItems().size() == 1 && schemaItem.isAllFieldsSimple()) {
// ------单表 & 所有字段都为简单字段------
singleTableSimpleFiledUpdate(config, dml, data, old);
} else {
// ------主表 查询sql来更新------
if (schemaItem.getMainTable().getTableName().equalsIgnoreCase(dml.getTable())) {
ESMapping mapping = config.getEsMapping();
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id();
FieldItem idFieldItem = schemaItem.getSelectFields().get(idFieldName);
boolean idFieldSimple = true;
if (idFieldItem.isMethod() || idFieldItem.isBinaryOp()) {
idFieldSimple = false;
}
boolean allUpdateFieldSimple = true;
out: for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
for (ColumnItem columnItem : fieldItem.getColumnItems()) {
if (old.containsKey(columnItem.getColumnName())) {
if (fieldItem.isMethod() || fieldItem.isBinaryOp()) {
allUpdateFieldSimple = false;
break out;
}
}
}
}
// 不支持主键更新!!
// 判断是否有外键更新
boolean fkChanged = false;
for (TableItem tableItem : schemaItem.getAliasTableItems().values()) {
if (tableItem.isMain()) {
continue;
}
boolean changed = false;
for (List<FieldItem> fieldItems : tableItem.getRelationTableFields().values()) {
for (FieldItem fieldItem : fieldItems) {
if (old.containsKey(fieldItem.getColumn().getColumnName())) {
fkChanged = true;
changed = true;
break;
}
}
}
// 如果外键有修改,则更新所对应该表的所有查询条件数据
if (changed) {
for (FieldItem fieldItem : tableItem.getRelationSelectFieldItems()) {
fieldItem.getColumnItems()
.forEach(columnItem -> old.put(columnItem.getColumnName(), null));
}
}
}
// 判断主键和所更新的字段是否全为简单字段
if (idFieldSimple && allUpdateFieldSimple && !fkChanged) {
singleTableSimpleFiledUpdate(config, dml, data, old);
} else {
mainTableUpdate(config, dml, data, old);
}
}
// 从表的操作
for (TableItem tableItem : schemaItem.getAliasTableItems().values()) {
if (tableItem.isMain()) {
continue;
}
if (!tableItem.getTableName().equals(dml.getTable())) {
continue;
}
// 关联条件出现在主表查询条件是否为简单字段
boolean allFieldsSimple = true;
for (FieldItem fieldItem : tableItem.getRelationSelectFieldItems()) {
if (fieldItem.isMethod() || fieldItem.isBinaryOp()) {
allFieldsSimple = false;
break;
}
}
// 所有查询字段均为简单字段
if (allFieldsSimple) {
// 不是子查询
if (!tableItem.isSubQuery()) {
// ------关联表简单字段更新------
Map<String, Object> esFieldData = new LinkedHashMap<>();
for (FieldItem fieldItem : tableItem.getRelationSelectFieldItems()) {
if (old.containsKey(fieldItem.getColumn().getColumnName())) {
Object value = esTemplate.getValFromData(config.getEsMapping(),
data,
fieldItem.getFieldName(),
fieldItem.getColumn().getColumnName());
esFieldData.put(Util.cleanColumn(fieldItem.getFieldName()), value);
}
}
joinTableSimpleFieldOperation(config, dml, data, tableItem, esFieldData);
} else {
// ------关联子表简单字段更新------
subTableSimpleFieldOperation(config, dml, data, old, tableItem);
}
} else {
// ------关联子表复杂字段更新 执行全sql更新es------
wholeSqlOperation(config, dml, data, old, tableItem);
}
}
}
i++;
}
}
/**
* 删除操作dml
*
* @param config es配置
* @param dml dml数据
*/
private void delete(ESSyncConfig config, Dml dml) {
List<Map<String, Object>> dataList = dml.getData();
if (dataList == null || dataList.isEmpty()) {
return;
}
SchemaItem schemaItem = config.getEsMapping().getSchemaItem();
for (Map<String, Object> data : dataList) {
if (data == null || data.isEmpty()) {
continue;
}
ESMapping mapping = config.getEsMapping();
// ------是主表------
if (schemaItem.getMainTable().getTableName().equalsIgnoreCase(dml.getTable())) {
if (mapping.get_id() != null) {
FieldItem idFieldItem = schemaItem.getIdFieldItem(mapping);
// 主键为简单字段
if (!idFieldItem.isMethod() && !idFieldItem.isBinaryOp()) {
Object idVal = esTemplate.getValFromData(mapping,
data,
idFieldItem.getFieldName(),
idFieldItem.getColumn().getColumnName());
if (logger.isTraceEnabled()) {
logger.trace("Main table delete es index, destination:{}, table: {}, index: {}, id: {}",
config.getDestination(),
dml.getTable(),
mapping.get_index(),
idVal);
}
esTemplate.delete(mapping, idVal, null);
} else {
// ------主键带函数, 查询sql获取主键删除------
// FIXME 删除时反查sql为空记录, 无法获获取 id field 值
mainTableDelete(config, dml, data);
}
} else {
FieldItem pkFieldItem = schemaItem.getIdFieldItem(mapping);
if (!pkFieldItem.isMethod() && !pkFieldItem.isBinaryOp()) {
Map<String, Object> esFieldData = new LinkedHashMap<>();
Object pkVal = esTemplate.getESDataFromDmlData(mapping, data, esFieldData);
if (logger.isTraceEnabled()) {
logger.trace("Main table delete es index, destination:{}, table: {}, index: {}, pk: {}",
config.getDestination(),
dml.getTable(),
mapping.get_index(),
pkVal);
}
esFieldData.remove(pkFieldItem.getFieldName());
esFieldData.keySet().forEach(key -> esFieldData.put(key, null));
esTemplate.delete(mapping, pkVal, esFieldData);
} else {
// ------主键带函数, 查询sql获取主键删除------
mainTableDelete(config, dml, data);
}
}
}
// 从表的操作
for (TableItem tableItem : schemaItem.getAliasTableItems().values()) {
if (tableItem.isMain()) {
continue;
}
if (!tableItem.getTableName().equals(dml.getTable())) {
continue;
}
// 关联条件出现在主表查询条件是否为简单字段
boolean allFieldsSimple = true;
for (FieldItem fieldItem : tableItem.getRelationSelectFieldItems()) {
if (fieldItem.isMethod() || fieldItem.isBinaryOp()) {
allFieldsSimple = false;
break;
}
}
// 所有查询字段均为简单字段
if (allFieldsSimple) {
// 不是子查询
if (!tableItem.isSubQuery()) {
// ------关联表简单字段更新为null------
Map<String, Object> esFieldData = new LinkedHashMap<>();
for (FieldItem fieldItem : tableItem.getRelationSelectFieldItems()) {
esFieldData.put(Util.cleanColumn(fieldItem.getFieldName()), null);
}
joinTableSimpleFieldOperation(config, dml, data, tableItem, esFieldData);
} else {
// ------关联子表简单字段更新------
subTableSimpleFieldOperation(config, dml, data, null, tableItem);
}
} else {
// ------关联子表复杂字段更新 执行全sql更新es------
wholeSqlOperation(config, dml, data, null, tableItem);
}
}
}
}
/**
* 单表简单字段insert
*
* @param config es配置
* @param dml dml信息
* @param data 单行dml数据
*/
private void singleTableSimpleFiledInsert(ESSyncConfig config, Dml dml, Map<String, Object> data) {
ESMapping mapping = config.getEsMapping();
Map<String, Object> esFieldData = new LinkedHashMap<>();
Object idVal = esTemplate.getESDataFromDmlData(mapping, data, esFieldData);
if (logger.isTraceEnabled()) {
logger.trace("Single table insert to es index, destination:{}, table: {}, index: {}, id: {}",
config.getDestination(),
dml.getTable(),
mapping.get_index(),
idVal);
}
esTemplate.insert(mapping, idVal, esFieldData);
}
/**
* 主表(单表)复杂字段insert
*
* @param config es配置
* @param dml dml信息
* @param data 单行dml数据
*/
private void mainTableInsert(ESSyncConfig config, Dml dml, Map<String, Object> data) {
ESMapping mapping = config.getEsMapping();
String sql = mapping.getSql();
String condition = ESSyncUtil.pkConditionSql(mapping, data);
sql = ESSyncUtil.appendCondition(sql, condition);
DataSource ds = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
if (logger.isTraceEnabled()) {
logger.trace("Main table insert to es index by query sql, destination:{}, table: {}, index: {}, sql: {}",
config.getDestination(),
dml.getTable(),
mapping.get_index(),
sql.replace("\n", " "));
}
Util.sqlRS(ds, sql, rs -> {
try {
while (rs.next()) {
Map<String, Object> esFieldData = new LinkedHashMap<>();
Object idVal = esTemplate.getESDataFromRS(mapping, rs, esFieldData);
if (logger.isTraceEnabled()) {
logger.trace(
"Main table insert to es index by query sql, destination:{}, table: {}, index: {}, id: {}",
config.getDestination(),
dml.getTable(),
mapping.get_index(),
idVal);
}
esTemplate.insert(mapping, idVal, esFieldData);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return 0;
});
}
private void mainTableDelete(ESSyncConfig config, Dml dml, Map<String, Object> data) {
ESMapping mapping = config.getEsMapping();
String sql = mapping.getSql();
String condition = ESSyncUtil.pkConditionSql(mapping, data);
sql = ESSyncUtil.appendCondition(sql, condition);
DataSource ds = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
if (logger.isTraceEnabled()) {
logger.trace("Main table delete es index by query sql, destination:{}, table: {}, index: {}, sql: {}",
config.getDestination(),
dml.getTable(),
mapping.get_index(),
sql.replace("\n", " "));
}
Util.sqlRS(ds, sql, rs -> {
try {
Map<String, Object> esFieldData = null;
if (mapping.getPk() != null) {
esFieldData = new LinkedHashMap<>();
esTemplate.getESDataFromDmlData(mapping, data, esFieldData);
esFieldData.remove(mapping.getPk());
for (String key : esFieldData.keySet()) {
esFieldData.put(Util.cleanColumn(key), null);
}
}
while (rs.next()) {
Object idVal = esTemplate.getIdValFromRS(mapping, rs);
if (logger.isTraceEnabled()) {
logger.trace(
"Main table delete to es index by query sql, destination:{}, table: {}, index: {}, id: {}",
config.getDestination(),
dml.getTable(),
mapping.get_index(),
idVal);
}
esTemplate.delete(mapping, idVal, esFieldData);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return 0;
});
}
/**
* 关联表主表简单字段operation
*
* @param config es配置
* @param dml dml信息
* @param data 单行dml数据
* @param tableItem 当前表配置
*/
private void joinTableSimpleFieldOperation(ESSyncConfig config, Dml dml, Map<String, Object> data,
TableItem tableItem, Map<String, Object> esFieldData) {
ESMapping mapping = config.getEsMapping();
Map<String, Object> paramsTmp = new LinkedHashMap<>();
for (Map.Entry<FieldItem, List<FieldItem>> entry : tableItem.getRelationTableFields().entrySet()) {
for (FieldItem fieldItem : entry.getValue()) {
if (fieldItem.getColumnItems().size() == 1) {
Object value = esTemplate.getValFromData(mapping,
data,
fieldItem.getFieldName(),
entry.getKey().getColumn().getColumnName());
String fieldName = fieldItem.getFieldName();
// 判断是否是主键
if (fieldName.equals(mapping.get_id())) {
fieldName = "_id";
}
paramsTmp.put(fieldName, value);
}
}
}
if (logger.isDebugEnabled()) {
logger.trace("Join table update es index by foreign key, destination:{}, table: {}, index: {}",
config.getDestination(),
dml.getTable(),
mapping.get_index());
}
esTemplate.updateByQuery(config, paramsTmp, esFieldData);
}
/**
* 关联子查询, 主表简单字段operation
*
* @param config es配置
* @param dml dml信息
* @param data 单行dml数据
* @param old 单行old数据
* @param tableItem 当前表配置
*/
private void subTableSimpleFieldOperation(ESSyncConfig config, Dml dml, Map<String, Object> data,
Map<String, Object> old, TableItem tableItem) {
ESMapping mapping = config.getEsMapping();
StringBuilder sql = new StringBuilder(
"SELECT * FROM (" + tableItem.getSubQuerySql() + ") " + tableItem.getAlias() + " WHERE ");
for (FieldItem fkFieldItem : tableItem.getRelationTableFields().keySet()) {
String columnName = fkFieldItem.getColumn().getColumnName();
Object value = esTemplate.getValFromData(mapping, data, fkFieldItem.getFieldName(), columnName);
ESSyncUtil.appendCondition(sql, value, tableItem.getAlias(), columnName);
}
int len = sql.length();
sql.delete(len - 5, len);
DataSource ds = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
if (logger.isTraceEnabled()) {
logger.trace("Join table update es index by query sql, destination:{}, table: {}, index: {}, sql: {}",
config.getDestination(),
dml.getTable(),
mapping.get_index(),
sql.toString().replace("\n", " "));
}
Util.sqlRS(ds, sql.toString(), rs -> {
try {
while (rs.next()) {
Map<String, Object> esFieldData = new LinkedHashMap<>();
for (FieldItem fieldItem : tableItem.getRelationSelectFieldItems()) {
if (old != null) {
out: for (FieldItem fieldItem1 : tableItem.getSubQueryFields()) {
for (ColumnItem columnItem0 : fieldItem.getColumnItems()) {
if (fieldItem1.getFieldName().equals(columnItem0.getColumnName()))
for (ColumnItem columnItem : fieldItem1.getColumnItems()) {
if (old.containsKey(columnItem.getColumnName())) {
Object val = esTemplate.getValFromRS(mapping,
rs,
fieldItem.getFieldName(),
fieldItem.getColumn().getColumnName());
esFieldData.put(Util.cleanColumn(fieldItem.getFieldName()), val);
break out;
}
}
}
}
} else {
Object val = esTemplate.getValFromRS(mapping,
rs,
fieldItem.getFieldName(),
fieldItem.getColumn().getColumnName());
esFieldData.put(Util.cleanColumn(fieldItem.getFieldName()), val);
}
}
Map<String, Object> paramsTmp = new LinkedHashMap<>();
for (Map.Entry<FieldItem, List<FieldItem>> entry : tableItem.getRelationTableFields().entrySet()) {
for (FieldItem fieldItem : entry.getValue()) {
if (fieldItem.getColumnItems().size() == 1) {
Object value = esTemplate.getValFromRS(mapping,
rs,
fieldItem.getFieldName(),
entry.getKey().getColumn().getColumnName());
String fieldName = fieldItem.getFieldName();
// 判断是否是主键
if (fieldName.equals(mapping.get_id())) {
fieldName = "_id";
}
paramsTmp.put(fieldName, value);
}
}
}
if (logger.isDebugEnabled()) {
logger.trace("Join table update es index by query sql, destination:{}, table: {}, index: {}",
config.getDestination(),
dml.getTable(),
mapping.get_index());
}
esTemplate.updateByQuery(config, paramsTmp, esFieldData);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return 0;
});
}
/**
* 关联(子查询), 主表复杂字段operation, 全sql执行
*
* @param config es配置
* @param dml dml信息
* @param data 单行dml数据
* @param tableItem 当前表配置
*/
private void wholeSqlOperation(ESSyncConfig config, Dml dml, Map<String, Object> data, Map<String, Object> old,
TableItem tableItem) {
ESMapping mapping = config.getEsMapping();
//防止最后出现groupby 导致sql解析异常
String[] sqlSplit = mapping.getSql().split("GROUP\\ BY(?!(.*)ON)");
String sqlNoWhere = sqlSplit[0];
String sqlGroupBy = "";
if(sqlSplit.length > 1){
sqlGroupBy = "GROUP BY "+ sqlSplit[1];
}
StringBuilder sql = new StringBuilder(sqlNoWhere + " WHERE ");
for (FieldItem fkFieldItem : tableItem.getRelationTableFields().keySet()) {
String columnName = fkFieldItem.getColumn().getColumnName();
Object value = esTemplate.getValFromData(mapping, data, fkFieldItem.getFieldName(), columnName);
ESSyncUtil.appendCondition(sql, value, tableItem.getAlias(), columnName);
}
int len = sql.length();
sql.delete(len - 5, len);
sql.append(sqlGroupBy);
DataSource ds = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
if (logger.isTraceEnabled()) {
logger.trace("Join table update es index by query whole sql, destination:{}, table: {}, index: {}, sql: {}",
config.getDestination(),
dml.getTable(),
mapping.get_index(),
sql.toString().replace("\n", " "));
}
Util.sqlRS(ds, sql.toString(), rs -> {
try {
while (rs.next()) {
Map<String, Object> esFieldData = new LinkedHashMap<>();
for (FieldItem fieldItem : tableItem.getRelationSelectFieldItems()) {
if (old != null) {
// 从表子查询
out: for (FieldItem fieldItem1 : tableItem.getSubQueryFields()) {
for (ColumnItem columnItem0 : fieldItem.getColumnItems()) {
if (fieldItem1.getFieldName().equals(columnItem0.getColumnName()))
for (ColumnItem columnItem : fieldItem1.getColumnItems()) {
if (old.containsKey(columnItem.getColumnName())) {
Object val = esTemplate.getValFromRS(mapping,
rs,
fieldItem.getFieldName(),
fieldItem.getFieldName());
esFieldData.put(fieldItem.getFieldName(), val);
break out;
}
}
}
}
// 从表非子查询
for (FieldItem fieldItem1 : tableItem.getRelationSelectFieldItems()) {
if (fieldItem1.equals(fieldItem)) {
for (ColumnItem columnItem : fieldItem1.getColumnItems()) {
if (old.containsKey(columnItem.getColumnName())) {
Object val = esTemplate.getValFromRS(mapping,
rs,
fieldItem.getFieldName(),
fieldItem.getFieldName());
esFieldData.put(Util.cleanColumn(fieldItem.getFieldName()), val);
break;
}
}
}
}
} else {
Object val = esTemplate
.getValFromRS(mapping, rs, fieldItem.getFieldName(), fieldItem.getFieldName());
esFieldData.put(Util.cleanColumn(fieldItem.getFieldName()), val);
}
}
Map<String, Object> paramsTmp = new LinkedHashMap<>();
for (Map.Entry<FieldItem, List<FieldItem>> entry : tableItem.getRelationTableFields().entrySet()) {
for (FieldItem fieldItem : entry.getValue()) {
Object value = esTemplate
.getValFromRS(mapping, rs, fieldItem.getFieldName(), fieldItem.getFieldName());
String fieldName = fieldItem.getFieldName();
// 判断是否是主键
if (fieldName.equals(mapping.get_id())) {
fieldName = "_id";
}
paramsTmp.put(fieldName, value);
}
}
if (logger.isDebugEnabled()) {
logger.trace(
"Join table update es index by query whole sql, destination:{}, table: {}, index: {}",
config.getDestination(),
dml.getTable(),
mapping.get_index());
}
esTemplate.updateByQuery(config, paramsTmp, esFieldData);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return 0;
});
}
/**
* 单表简单字段update
*
* @param config es配置
* @param dml dml信息
* @param data 单行data数据
* @param old 单行old数据
*/
private void singleTableSimpleFiledUpdate(ESSyncConfig config, Dml dml, Map<String, Object> data,
Map<String, Object> old) {
ESMapping mapping = config.getEsMapping();
Map<String, Object> esFieldData = new LinkedHashMap<>();
Object idVal = esTemplate.getESDataFromDmlData(mapping, data, old, esFieldData);
if (logger.isTraceEnabled()) {
logger.trace("Main table update to es index, destination:{}, table: {}, index: {}, id: {}",
config.getDestination(),
dml.getTable(),
mapping.get_index(),
idVal);
}
esTemplate.update(mapping, idVal, esFieldData);
}
/**
* 主表(单表)复杂字段update
*
* @param config es配置
* @param dml dml信息
* @param data 单行dml数据
*/
private void mainTableUpdate(ESSyncConfig config, Dml dml, Map<String, Object> data, Map<String, Object> old) {
ESMapping mapping = config.getEsMapping();
String sql = mapping.getSql();
String condition = ESSyncUtil.pkConditionSql(mapping, data);
sql = ESSyncUtil.appendCondition(sql, condition);
DataSource ds = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
if (logger.isTraceEnabled()) {
logger.trace("Main table update to es index by query sql, destination:{}, table: {}, index: {}, sql: {}",
config.getDestination(),
dml.getTable(),
mapping.get_index(),
sql.replace("\n", " "));
}
Util.sqlRS(ds, sql, rs -> {
try {
while (rs.next()) {
Map<String, Object> esFieldData = new LinkedHashMap<>();
Object idVal = esTemplate.getESDataFromRS(mapping, rs, old, esFieldData);
if (logger.isTraceEnabled()) {
logger.trace(
"Main table update to es index by query sql, destination:{}, table: {}, index: {}, id: {}",
config.getDestination(),
dml.getTable(),
mapping.get_index(),
idVal);
}
esTemplate.update(mapping, idVal, esFieldData);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return 0;
});
}
/**
* 提交批次
*/
public void commit() {
esTemplate.commit();
}
}
@@ -1,306 +0,0 @@
package com.alibaba.otter.canal.client.adapter.es.support;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.sql.Blob;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.codec.binary.Base64;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig.ESMapping;
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem;
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.ColumnItem;
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.TableItem;
import com.alibaba.otter.canal.client.adapter.support.Util;
/**
* ES 同步工具同类
*
* @author rewerma 2018-11-01
* @version 1.0.0
*/
public class ESSyncUtil {
private static Logger logger = LoggerFactory.getLogger(ESSyncUtil.class);
public static Object convertToEsObj(Object val, String fieldInfo) {
if (val == null) {
return null;
}
if (fieldInfo.startsWith("array:")) {
String separator = fieldInfo.substring("array:".length()).trim();
String[] values = val.toString().split(separator);
return Arrays.asList(values);
} else if (fieldInfo.startsWith("object")) {
return JSON.parse(val.toString());
}
return null;
}
/**
* 类型转换为Mapping中对应的类型
*/
public static Object typeConvert(Object val, String esType) {
if (val == null) {
return null;
}
if (esType == null) {
return val;
}
Object res = null;
if ("integer".equals(esType)) {
if (val instanceof Number) {
res = ((Number) val).intValue();
} else {
res = Integer.parseInt(val.toString());
}
} else if ("long".equals(esType)) {
if (val instanceof Number) {
res = ((Number) val).longValue();
} else {
res = Long.parseLong(val.toString());
}
} else if ("short".equals(esType)) {
if (val instanceof Number) {
res = ((Number) val).shortValue();
} else {
res = Short.parseShort(val.toString());
}
} else if ("byte".equals(esType)) {
if (val instanceof Number) {
res = ((Number) val).byteValue();
} else {
res = Byte.parseByte(val.toString());
}
} else if ("double".equals(esType)) {
if (val instanceof Number) {
res = ((Number) val).doubleValue();
} else {
res = Double.parseDouble(val.toString());
}
} else if ("float".equals(esType) || "half_float".equals(esType) || "scaled_float".equals(esType)) {
if (val instanceof Number) {
res = ((Number) val).floatValue();
} else {
res = Float.parseFloat(val.toString());
}
} else if ("boolean".equals(esType)) {
if (val instanceof Boolean) {
res = val;
} else if (val instanceof Number) {
int v = ((Number) val).intValue();
res = v != 0;
} else {
res = Boolean.parseBoolean(val.toString());
}
} else if ("date".equals(esType)) {
if (val instanceof java.sql.Time) {
DateTime dateTime = new DateTime(((java.sql.Time) val).getTime());
if (dateTime.getMillisOfSecond() != 0) {
res = dateTime.toString("HH:mm:ss.SSS");
} else {
res = dateTime.toString("HH:mm:ss");
}
} else if (val instanceof java.sql.Timestamp) {
DateTime dateTime = new DateTime(((java.sql.Timestamp) val).getTime());
if (dateTime.getMillisOfSecond() != 0) {
res = dateTime.toString("yyyy-MM-dd'T'HH:mm:ss.SSS" + Util.timeZone);
} else {
res = dateTime.toString("yyyy-MM-dd'T'HH:mm:ss" + Util.timeZone);
}
} else if (val instanceof java.sql.Date || val instanceof Date) {
DateTime dateTime;
if (val instanceof java.sql.Date) {
dateTime = new DateTime(((java.sql.Date) val).getTime());
} else {
dateTime = new DateTime(((Date) val).getTime());
}
if (dateTime.getHourOfDay() == 0 && dateTime.getMinuteOfHour() == 0 && dateTime.getSecondOfMinute() == 0
&& dateTime.getMillisOfSecond() == 0) {
res = dateTime.toString("yyyy-MM-dd");
} else {
if (dateTime.getMillisOfSecond() != 0) {
res = dateTime.toString("yyyy-MM-dd'T'HH:mm:ss.SSS" + Util.timeZone);
} else {
res = dateTime.toString("yyyy-MM-dd'T'HH:mm:ss" + Util.timeZone);
}
}
} else if (val instanceof Long) {
DateTime dateTime = new DateTime(((Long) val).longValue());
if (dateTime.getHourOfDay() == 0 && dateTime.getMinuteOfHour() == 0 && dateTime.getSecondOfMinute() == 0
&& dateTime.getMillisOfSecond() == 0) {
res = dateTime.toString("yyyy-MM-dd");
} else if (dateTime.getMillisOfSecond() != 0) {
res = dateTime.toString("yyyy-MM-dd'T'HH:mm:ss.SSS" + Util.timeZone);
} else {
res = dateTime.toString("yyyy-MM-dd'T'HH:mm:ss" + Util.timeZone);
}
} else if (val instanceof String) {
String v = ((String) val).trim();
if (v.length() > 18 && v.charAt(4) == '-' && v.charAt(7) == '-' && v.charAt(10) == ' '
&& v.charAt(13) == ':' && v.charAt(16) == ':') {
String dt = v.substring(0, 10) + "T" + v.substring(11);
Date date = Util.parseDate(dt);
if (date != null) {
DateTime dateTime = new DateTime(date);
if (dateTime.getMillisOfSecond() != 0) {
res = dateTime.toString("yyyy-MM-dd'T'HH:mm:ss.SSS" + Util.timeZone);
} else {
res = dateTime.toString("yyyy-MM-dd'T'HH:mm:ss" + Util.timeZone);
}
}
} else if (v.length() == 10 && v.charAt(4) == '-' && v.charAt(7) == '-') {
Date date = Util.parseDate(v);
if (date != null) {
DateTime dateTime = new DateTime(date);
res = dateTime.toString("yyyy-MM-dd");
}
}
}
} else if ("binary".equals(esType)) {
if (val instanceof byte[]) {
Base64 base64 = new Base64();
res = base64.encodeAsString((byte[]) val);
} else if (val instanceof Blob) {
byte[] b = blobToBytes((Blob) val);
Base64 base64 = new Base64();
res = base64.encodeAsString(b);
} else if (val instanceof String) {
// 对应canal中的单字节编码
byte[] b = ((String) val).getBytes(StandardCharsets.ISO_8859_1);
Base64 base64 = new Base64();
res = base64.encodeAsString(b);
}
} else if ("geo_point".equals(esType)) {
if (!(val instanceof String)) {
logger.error("es type is geo_point, but source type is not String");
return val;
}
if (!((String) val).contains(",")) {
logger.error("es type is geo_point, source value not contains ',' separator");
return val;
}
String[] point = ((String) val).split(",");
Map<String, Double> location = new HashMap<>();
location.put("lat", Double.valueOf(point[0].trim()));
location.put("lon", Double.valueOf(point[1].trim()));
return location;
} else if ("array".equals(esType)) {
if ("".equals(val.toString().trim())) {
res = new ArrayList<>();
} else {
String value = val.toString();
String separator = ",";
if (!value.contains(",")) {
if (value.contains(";")) {
separator = ";";
} else if (value.contains("|")) {
separator = "\\|";
} else if (value.contains("-")) {
separator = "-";
}
}
String[] values = value.split(separator);
return Arrays.asList(values);
}
} else if ("object".equals(esType)) {
if ("".equals(val.toString().trim())) {
res = new HashMap<>();
} else {
res = JSON.parseObject(val.toString(), Map.class);
}
} else {
// 其他类全以字符串处理
res = val.toString();
}
return res;
}
/**
* Blob转byte[]
*/
private static byte[] blobToBytes(Blob blob) {
try (InputStream is = blob.getBinaryStream()) {
byte[] b = new byte[(int) blob.length()];
if (is.read(b) != -1) {
return b;
} else {
return new byte[0];
}
} catch (IOException | SQLException e) {
logger.error(e.getMessage());
return null;
}
}
/**
* 拼接主键条件
*
* @param mapping
* @param data
* @return
*/
public static String pkConditionSql(ESMapping mapping, Map<String, Object> data) {
Set<ColumnItem> idColumns = new LinkedHashSet<>();
SchemaItem schemaItem = mapping.getSchemaItem();
TableItem mainTable = schemaItem.getMainTable();
for (ColumnItem idColumnItem : schemaItem.getIdFieldItem(mapping).getColumnItems()) {
if ((mainTable.getAlias() == null && idColumnItem.getOwner() == null)
|| (mainTable.getAlias() != null && mainTable.getAlias().equals(idColumnItem.getOwner()))) {
idColumns.add(idColumnItem);
}
}
if (idColumns.isEmpty()) {
throw new RuntimeException("Not found primary key field in main table");
}
// 拼接condition
StringBuilder condition = new StringBuilder(" ");
for (ColumnItem idColumn : idColumns) {
Object idVal = data.get(idColumn.getColumnName());
if (mainTable.getAlias() != null) condition.append(mainTable.getAlias()).append(".");
condition.append(idColumn.getColumnName()).append("=");
if (idVal instanceof String) {
condition.append("'").append(idVal).append("' AND ");
} else {
condition.append(idVal).append(" AND ");
}
}
if (condition.toString().endsWith("AND ")) {
int len2 = condition.length();
condition.delete(len2 - 4, len2);
}
return condition.toString();
}
public static String appendCondition(String sql, String condition) {
return sql + " WHERE " + condition + " ";
}
public static void appendCondition(StringBuilder sql, Object value, String owner, String columnName) {
if (value instanceof String) {
sql.append(owner).append(".").append(columnName).append("='").append(value).append("' AND ");
} else {
sql.append(owner).append(".").append(columnName).append("=").append(value).append(" AND ");
}
}
}
@@ -1,531 +0,0 @@
package com.alibaba.otter.canal.client.adapter.es.support;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.sql.DataSource;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequestBuilder;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.cluster.metadata.MappingMetaData;
import org.elasticsearch.common.collect.ImmutableOpenMap;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchHit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig;
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig.ESMapping;
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem;
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.ColumnItem;
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.FieldItem;
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
import com.alibaba.otter.canal.client.adapter.support.Util;
/**
* ES 操作模板
*
* @author rewerma 2018-11-01
* @version 1.0.0
*/
public class ESTemplate {
private static final Logger logger = LoggerFactory.getLogger(ESTemplate.class);
private static final int MAX_BATCH_SIZE = 1000;
private TransportClient transportClient;
private BulkRequestBuilder bulkRequestBuilder;
public ESTemplate(TransportClient transportClient){
this.transportClient = transportClient;
this.bulkRequestBuilder = transportClient.prepareBulk();
}
public BulkRequestBuilder getBulk() {
return bulkRequestBuilder;
}
public void resetBulkRequestBuilder() {
this.bulkRequestBuilder = this.transportClient.prepareBulk();
}
/**
* 插入数据
*
* @param mapping 配置对象
* @param pkVal 主键值
* @param esFieldData 数据Map
*/
public void insert(ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) {
if (mapping.get_id() != null) {
String parentVal = (String) esFieldData.remove("$parent_routing");
if (mapping.isUpsert()) {
UpdateRequestBuilder updateRequestBuilder = transportClient
.prepareUpdate(mapping.get_index(), mapping.get_type(), pkVal.toString())
.setDoc(esFieldData)
.setDocAsUpsert(true);
if (StringUtils.isNotEmpty(parentVal)) {
updateRequestBuilder.setRouting(parentVal);
}
getBulk().add(updateRequestBuilder);
} else {
IndexRequestBuilder indexRequestBuilder = transportClient
.prepareIndex(mapping.get_index(), mapping.get_type(), pkVal.toString())
.setSource(esFieldData);
if (StringUtils.isNotEmpty(parentVal)) {
indexRequestBuilder.setRouting(parentVal);
}
getBulk().add(indexRequestBuilder);
}
commitBulk();
} else {
SearchResponse response = transportClient.prepareSearch(mapping.get_index())
.setTypes(mapping.get_type())
.setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal))
.setSize(10000)
.get();
for (SearchHit hit : response.getHits()) {
getBulk().add(transportClient.prepareUpdate(mapping.get_index(), mapping.get_type(), hit.getId())
.setDoc(esFieldData));
commitBulk();
}
}
}
/**
* 根据主键更新数据
*
* @param mapping 配置对象
* @param pkVal 主键值
* @param esFieldData 数据Map
*/
public void update(ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) {
Map<String, Object> esFieldDataTmp = new LinkedHashMap<>(esFieldData.size());
esFieldData.forEach((k, v) -> esFieldDataTmp.put(Util.cleanColumn(k), v));
append4Update(mapping, pkVal, esFieldDataTmp);
commitBulk();
}
/**
* update by query
*
* @param config 配置对象
* @param paramsTmp sql查询条件
* @param esFieldData 数据Map
*/
public void updateByQuery(ESSyncConfig config, Map<String, Object> paramsTmp, Map<String, Object> esFieldData) {
if (paramsTmp.isEmpty()) {
return;
}
ESMapping mapping = config.getEsMapping();
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
paramsTmp.forEach((fieldName, value) -> queryBuilder.must(QueryBuilders.termsQuery(fieldName, value)));
// 查询sql批量更新
DataSource ds = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
StringBuilder sql = new StringBuilder("SELECT * FROM (" + mapping.getSql() + ") _v WHERE ");
List<Object> values = new ArrayList<>();
paramsTmp.forEach((fieldName, value) -> {
sql.append("_v.").append(fieldName).append("=? AND ");
values.add(value);
});
//TODO 直接外部包裹sql会导致全表扫描性能低, 待优化拼接内部where条件
int len = sql.length();
sql.delete(len - 4, len);
Integer syncCount = (Integer) Util.sqlRS(ds, sql.toString(), values, rs -> {
int count = 0;
try {
while (rs.next()) {
Object idVal = getIdValFromRS(mapping, rs);
append4Update(mapping, idVal, esFieldData);
commitBulk();
count++;
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return count;
});
if (logger.isTraceEnabled()) {
logger.trace("Update ES by query affected {} records", syncCount);
}
}
/**
* 通过主键删除数据
*
* @param mapping 配置对象
* @param pkVal 主键值
* @param esFieldData 数据Map
*/
public void delete(ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) {
if (mapping.get_id() != null) {
getBulk().add(transportClient.prepareDelete(mapping.get_index(), mapping.get_type(), pkVal.toString()));
commitBulk();
} else {
SearchResponse response = transportClient.prepareSearch(mapping.get_index())
.setTypes(mapping.get_type())
.setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal))
.setSize(10000)
.get();
for (SearchHit hit : response.getHits()) {
getBulk().add(transportClient.prepareUpdate(mapping.get_index(), mapping.get_type(), hit.getId())
.setDoc(esFieldData));
commitBulk();
}
}
}
/**
* 提交批次
*/
public void commit() {
if (getBulk().numberOfActions() > 0) {
BulkResponse response = getBulk().execute().actionGet();
if (response.hasFailures()) {
for (BulkItemResponse itemResponse : response.getItems()) {
if (!itemResponse.isFailed()) {
continue;
}
if (itemResponse.getFailure().getStatus() == RestStatus.NOT_FOUND) {
logger.error(itemResponse.getFailureMessage());
} else {
throw new RuntimeException("ES sync commit error" + itemResponse.getFailureMessage());
}
}
}
}
}
/**
* 如果大于批量数则提交批次, 调用后es bulk请求后,numberOfActions不会清理,需要主动调用函数清0,否则不能起到批量请求的效果
*/
private void commitBulk() {
if (getBulk().numberOfActions() >= MAX_BATCH_SIZE) {
commit();
resetBulkRequestBuilder();
}
}
private void append4Update(ESMapping mapping, Object pkVal, Map<String, Object> esFieldData) {
if (mapping.get_id() != null) {
String parentVal = (String) esFieldData.remove("$parent_routing");
if (mapping.isUpsert()) {
UpdateRequestBuilder updateRequestBuilder = transportClient
.prepareUpdate(mapping.get_index(), mapping.get_type(), pkVal.toString())
.setDoc(esFieldData)
.setDocAsUpsert(true);
if (StringUtils.isNotEmpty(parentVal)) {
updateRequestBuilder.setRouting(parentVal);
}
getBulk().add(updateRequestBuilder);
} else {
UpdateRequestBuilder updateRequestBuilder = transportClient
.prepareUpdate(mapping.get_index(), mapping.get_type(), pkVal.toString())
.setDoc(esFieldData);
if (StringUtils.isNotEmpty(parentVal)) {
updateRequestBuilder.setRouting(parentVal);
}
getBulk().add(updateRequestBuilder);
}
} else {
SearchResponse response = transportClient.prepareSearch(mapping.get_index())
.setTypes(mapping.get_type())
.setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal))
.setSize(10000)
.get();
for (SearchHit hit : response.getHits()) {
getBulk().add(transportClient.prepareUpdate(mapping.get_index(), mapping.get_type(), hit.getId())
.setDoc(esFieldData));
}
}
}
public Object getValFromRS(ESMapping mapping, ResultSet resultSet, String fieldName,
String columnName) throws SQLException {
fieldName = Util.cleanColumn(fieldName);
columnName = Util.cleanColumn(columnName);
String esType = getEsType(mapping, fieldName);
Object value = resultSet.getObject(columnName);
if (value instanceof Boolean) {
if (!"boolean".equals(esType)) {
value = resultSet.getByte(columnName);
}
}
// 如果是对象类型
if (mapping.getObjFields().containsKey(fieldName)) {
return ESSyncUtil.convertToEsObj(value, mapping.getObjFields().get(fieldName));
} else {
return ESSyncUtil.typeConvert(value, esType);
}
}
public Object getESDataFromRS(ESMapping mapping, ResultSet resultSet,
Map<String, Object> esFieldData) throws SQLException {
SchemaItem schemaItem = mapping.getSchemaItem();
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id();
Object resultIdVal = null;
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
Object value = getValFromRS(mapping, resultSet, fieldItem.getFieldName(), fieldItem.getFieldName());
if (fieldItem.getFieldName().equals(idFieldName)) {
resultIdVal = value;
}
if (!fieldItem.getFieldName().equals(mapping.get_id())
&& !mapping.getSkips().contains(fieldItem.getFieldName())) {
esFieldData.put(Util.cleanColumn(fieldItem.getFieldName()), value);
}
}
// 添加父子文档关联信息
putRelationDataFromRS(mapping, schemaItem, resultSet, esFieldData);
return resultIdVal;
}
public Object getIdValFromRS(ESMapping mapping, ResultSet resultSet) throws SQLException {
SchemaItem schemaItem = mapping.getSchemaItem();
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id();
Object resultIdVal = null;
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
Object value = getValFromRS(mapping, resultSet, fieldItem.getFieldName(), fieldItem.getFieldName());
if (fieldItem.getFieldName().equals(idFieldName)) {
resultIdVal = value;
break;
}
}
return resultIdVal;
}
public Object getESDataFromRS(ESMapping mapping, ResultSet resultSet, Map<String, Object> dmlOld,
Map<String, Object> esFieldData) throws SQLException {
SchemaItem schemaItem = mapping.getSchemaItem();
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id();
Object resultIdVal = null;
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
if (fieldItem.getFieldName().equals(idFieldName)) {
resultIdVal = getValFromRS(mapping, resultSet, fieldItem.getFieldName(), fieldItem.getFieldName());
}
for (ColumnItem columnItem : fieldItem.getColumnItems()) {
if (dmlOld.containsKey(columnItem.getColumnName())
&& !mapping.getSkips().contains(fieldItem.getFieldName())) {
esFieldData.put(Util.cleanColumn(fieldItem.getFieldName()),
getValFromRS(mapping, resultSet, fieldItem.getFieldName(), fieldItem.getFieldName()));
break;
}
}
}
// 添加父子文档关联信息
putRelationDataFromRS(mapping, schemaItem, resultSet, esFieldData);
return resultIdVal;
}
public Object getValFromData(ESMapping mapping, Map<String, Object> dmlData, String fieldName, String columnName) {
String esType = getEsType(mapping, fieldName);
Object value = dmlData.get(columnName);
if (value instanceof Byte) {
if ("boolean".equals(esType)) {
value = ((Byte) value).intValue() != 0;
}
}
// 如果是对象类型
if (mapping.getObjFields().containsKey(fieldName)) {
return ESSyncUtil.convertToEsObj(value, mapping.getObjFields().get(fieldName));
} else {
return ESSyncUtil.typeConvert(value, esType);
}
}
/**
* 将dml的data转换为es的data
*
* @param mapping 配置mapping
* @param dmlData dml data
* @param esFieldData es data
* @return 返回 id 值
*/
public Object getESDataFromDmlData(ESMapping mapping, Map<String, Object> dmlData,
Map<String, Object> esFieldData) {
SchemaItem schemaItem = mapping.getSchemaItem();
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id();
Object resultIdVal = null;
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
String columnName = fieldItem.getColumnItems().iterator().next().getColumnName();
Object value = getValFromData(mapping, dmlData, fieldItem.getFieldName(), columnName);
if (fieldItem.getFieldName().equals(idFieldName)) {
resultIdVal = value;
}
if (!fieldItem.getFieldName().equals(mapping.get_id())
&& !mapping.getSkips().contains(fieldItem.getFieldName())) {
esFieldData.put(Util.cleanColumn(fieldItem.getFieldName()), value);
}
}
// 添加父子文档关联信息
putRelationData(mapping, schemaItem, dmlData, esFieldData);
return resultIdVal;
}
/**
* 将dml的data, old转换为es的data
*
* @param mapping 配置mapping
* @param dmlData dml data
* @param esFieldData es data
* @return 返回 id 值
*/
public Object getESDataFromDmlData(ESMapping mapping, Map<String, Object> dmlData, Map<String, Object> dmlOld,
Map<String, Object> esFieldData) {
SchemaItem schemaItem = mapping.getSchemaItem();
String idFieldName = mapping.get_id() == null ? mapping.getPk() : mapping.get_id();
Object resultIdVal = null;
for (FieldItem fieldItem : schemaItem.getSelectFields().values()) {
String columnName = fieldItem.getColumnItems().iterator().next().getColumnName();
if (fieldItem.getFieldName().equals(idFieldName)) {
resultIdVal = getValFromData(mapping, dmlData, fieldItem.getFieldName(), columnName);
}
if (dmlOld.containsKey(columnName) && !mapping.getSkips().contains(fieldItem.getFieldName())) {
esFieldData.put(Util.cleanColumn(fieldItem.getFieldName()),
getValFromData(mapping, dmlData, fieldItem.getFieldName(), columnName));
}
}
// 添加父子文档关联信息
putRelationData(mapping, schemaItem, dmlOld, esFieldData);
return resultIdVal;
}
private void putRelationDataFromRS(ESMapping mapping, SchemaItem schemaItem, ResultSet resultSet,
Map<String, Object> esFieldData) {
// 添加父子文档关联信息
if (!mapping.getRelations().isEmpty()) {
mapping.getRelations().forEach((relationField, relationMapping) -> {
Map<String, Object> relations = new HashMap<>();
relations.put("name", relationMapping.getName());
if (StringUtils.isNotEmpty(relationMapping.getParent())) {
FieldItem parentFieldItem = schemaItem.getSelectFields().get(relationMapping.getParent());
Object parentVal;
try {
parentVal = getValFromRS(mapping,
resultSet,
parentFieldItem.getFieldName(),
parentFieldItem.getFieldName());
} catch (SQLException e) {
throw new RuntimeException(e);
}
if (parentVal != null) {
relations.put("parent", parentVal.toString());
esFieldData.put("$parent_routing", parentVal.toString());
}
}
esFieldData.put(relationField, relations);
});
}
}
private void putRelationData(ESMapping mapping, SchemaItem schemaItem, Map<String, Object> dmlData,
Map<String, Object> esFieldData) {
// 添加父子文档关联信息
if (!mapping.getRelations().isEmpty()) {
mapping.getRelations().forEach((relationField, relationMapping) -> {
Map<String, Object> relations = new HashMap<>();
relations.put("name", relationMapping.getName());
if (StringUtils.isNotEmpty(relationMapping.getParent())) {
FieldItem parentFieldItem = schemaItem.getSelectFields().get(relationMapping.getParent());
String columnName = parentFieldItem.getColumnItems().iterator().next().getColumnName();
Object parentVal = getValFromData(mapping, dmlData, parentFieldItem.getFieldName(), columnName);
if (parentVal != null) {
relations.put("parent", parentVal.toString());
esFieldData.put("$parent_routing", parentVal.toString());
}
}
esFieldData.put(relationField, relations);
});
}
}
/**
* es 字段类型本地缓存
*/
private static ConcurrentMap<String, Map<String, String>> esFieldTypes = new ConcurrentHashMap<>();
/**
* 获取es mapping中的属性类型
*
* @param mapping mapping配置
* @param fieldName 属性名
* @return 类型
*/
@SuppressWarnings("unchecked")
private String getEsType(ESMapping mapping, String fieldName) {
String key = mapping.get_index() + "-" + mapping.get_type();
Map<String, String> fieldType = esFieldTypes.get(key);
if (fieldType == null) {
ImmutableOpenMap<String, MappingMetaData> mappings;
try {
mappings = transportClient.admin()
.cluster()
.prepareState()
.execute()
.actionGet()
.getState()
.getMetaData()
.getIndices()
.get(mapping.get_index())
.getMappings();
} catch (NullPointerException e) {
throw new IllegalArgumentException("Not found the mapping info of index: " + mapping.get_index());
}
MappingMetaData mappingMetaData = mappings.get(mapping.get_type());
if (mappingMetaData == null) {
throw new IllegalArgumentException("Not found the mapping info of index: " + mapping.get_index());
}
fieldType = new LinkedHashMap<>();
Map<String, Object> sourceMap = mappingMetaData.getSourceAsMap();
Map<String, Object> esMapping = (Map<String, Object>) sourceMap.get("properties");
for (Map.Entry<String, Object> entry : esMapping.entrySet()) {
Map<String, Object> value = (Map<String, Object>) entry.getValue();
if (value.containsKey("properties")) {
fieldType.put(entry.getKey(), "object");
} else {
fieldType.put(entry.getKey(), (String) value.get("type"));
}
}
esFieldTypes.put(key, fieldType);
}
return fieldType.get(fieldName);
}
}
@@ -1 +0,0 @@
es=com.alibaba.otter.canal.client.adapter.es.ESAdapter
@@ -1,21 +0,0 @@
dataSourceKey: defaultDS
destination: example
groupId: g1
esMapping:
_index: customer
_type: _doc
_id: _id
relations:
customer_order:
name: order
parent: customer_id
sql: "select concat('oid_', t.id) as _id,
t.customer_id,
t.id as order_id,
t.serial_code as order_serial,
t.c_time as order_time
from biz_order t"
skips:
- customer_id
etlCondition: "where t.c_time>={}"
commitBatch: 3000
@@ -1,47 +0,0 @@
dataSourceKey: defaultDS
destination: example
groupId: g1
esMapping:
_index: customer
_type: _doc
_id: id
relations:
customer_order:
name: customer
sql: "select t.id, t.name, t.email from customer t"
etlCondition: "where t.c_time>={}"
commitBatch: 3000
#{
# "mappings":{
# "_doc":{
# "properties":{
# "id": {
# "type": "long"
# },
# "name": {
# "type": "text"
# },
# "email": {
# "type": "text"
# },
# "order_id": {
# "type": "long"
# },
# "order_serial": {
# "type": "text"
# },
# "order_time": {
# "type": "date"
# },
# "customer_order":{
# "type":"join",
# "relations":{
# "customer":"order"
# }
# }
# }
# }
# }
#}
@@ -1,16 +0,0 @@
dataSourceKey: defaultDS
destination: example
groupId: g1
esMapping:
_index: mytest_user
_type: _doc
_id: _id
upsert: true
# pk: id
sql: "select a.id as _id, a.name as _name, a.role_id as _role_id, b.role_name as _role_name,
a.c_time as _c_time from user a
left join role b on b.id=a.role_id"
# objFields:
# _labels: array:;
etlCondition: "where a.c_time>={}"
commitBatch: 3000
@@ -1,40 +0,0 @@
package com.alibaba.otter.canal.client.adapter.es.test;
import java.util.Map;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig;
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfigLoader;
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
@Ignore
public class ConfigLoadTest {
@Before
public void before() {
// AdapterConfigs.put("es", "mytest_user.yml");
// 加载数据源连接池
DatasourceConfig.DATA_SOURCES.put("defaultDS", TestConstant.dataSource);
}
@Test
public void testLoad() {
Map<String, ESSyncConfig> configMap = ESSyncConfigLoader.load(null);
ESSyncConfig config = configMap.get("mytest_user.yml");
config.validate();
Assert.assertNotNull(config);
Assert.assertEquals("defaultDS", config.getDataSourceKey());
ESSyncConfig.ESMapping esMapping = config.getEsMapping();
Assert.assertEquals("mytest_user", esMapping.get_index());
Assert.assertEquals("_doc", esMapping.get_type());
Assert.assertEquals("id", esMapping.get_id());
Assert.assertNotNull(esMapping.getSql());
// Map<String, List<ESSyncConfig>> dbTableEsSyncConfig =
// ESSyncConfigLoader.getDbTableEsSyncConfig();
// Assert.assertFalse(dbTableEsSyncConfig.isEmpty());
}
}
@@ -1,119 +0,0 @@
package com.alibaba.otter.canal.client.adapter.es.test;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.LinkedHashMap;
import java.util.Map;
import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.transport.client.PreBuiltTransportClient;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
@Ignore
public class ESTest {
private TransportClient transportClient;
@Before
public void init() throws UnknownHostException {
Settings.Builder settingBuilder = Settings.builder();
settingBuilder.put("cluster.name", TestConstant.clusterName);
Settings settings = settingBuilder.build();
transportClient = new PreBuiltTransportClient(settings);
String[] hostArray = TestConstant.esHosts.split(",");
for (String host : hostArray) {
int i = host.indexOf(":");
transportClient.addTransportAddress(new TransportAddress(InetAddress.getByName(host.substring(0, i)),
Integer.parseInt(host.substring(i + 1))));
}
}
@Test
public void test01() {
SearchResponse response = transportClient.prepareSearch("test")
.setTypes("osm")
.setQuery(QueryBuilders.termQuery("_id", "1"))
.setSize(10000)
.get();
for (SearchHit hit : response.getHits()) {
System.out.println(hit.getSourceAsMap().get("data").getClass());
}
}
@Test
public void test02() {
Map<String, Object> esFieldData = new LinkedHashMap<>();
esFieldData.put("userId", 2L);
esFieldData.put("eventId", 4L);
esFieldData.put("eventName", "网络异常");
esFieldData.put("description", "第四个事件信息");
Map<String, Object> relations = new LinkedHashMap<>();
esFieldData.put("user_event", relations);
relations.put("name", "event");
relations.put("parent", "2");
BulkRequestBuilder bulkRequestBuilder = transportClient.prepareBulk();
bulkRequestBuilder
.add(transportClient.prepareIndex("test", "osm", "2_4").setRouting("2").setSource(esFieldData));
commit(bulkRequestBuilder);
}
@Test
public void test03() {
Map<String, Object> esFieldData = new LinkedHashMap<>();
esFieldData.put("userId", 2L);
esFieldData.put("eventName", "网络异常1");
Map<String, Object> relations = new LinkedHashMap<>();
esFieldData.put("user_event", relations);
relations.put("name", "event");
relations.put("parent", "2");
BulkRequestBuilder bulkRequestBuilder = transportClient.prepareBulk();
bulkRequestBuilder.add(transportClient.prepareUpdate("test", "osm", "2_4").setRouting("2").setDoc(esFieldData));
commit(bulkRequestBuilder);
}
@Test
public void test04() {
BulkRequestBuilder bulkRequestBuilder = transportClient.prepareBulk();
bulkRequestBuilder.add(transportClient.prepareDelete("test", "osm", "2_4"));
commit(bulkRequestBuilder);
}
private void commit(BulkRequestBuilder bulkRequestBuilder) {
if (bulkRequestBuilder.numberOfActions() > 0) {
BulkResponse response = bulkRequestBuilder.execute().actionGet();
if (response.hasFailures()) {
for (BulkItemResponse itemResponse : response.getItems()) {
if (!itemResponse.isFailed()) {
continue;
}
if (itemResponse.getFailure().getStatus() == RestStatus.NOT_FOUND) {
System.out.println(itemResponse.getFailureMessage());
} else {
System.out.println("ES bulk commit error" + itemResponse.getFailureMessage());
}
}
}
}
}
@After
public void after() {
transportClient.close();
}
}
@@ -1,48 +0,0 @@
package com.alibaba.otter.canal.client.adapter.es.test;
import java.util.List;
import java.util.Map;
import org.junit.Assert;
import org.junit.Test;
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem;
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.FieldItem;
import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.TableItem;
import com.alibaba.otter.canal.client.adapter.es.config.SqlParser;
public class SqlParseTest {
@Test
public void parseTest() {
String sql = "select a.id, CASE WHEN a.id <= 500 THEN '1' else '2' end as id2, "
+ "concat(a.name,'_test') as name, a.role_id, b.name as role_name, c.labels from user a "
+ "left join role b on a.role_id=b.id "
+ "left join (select user_id, group_concat(label,',') as labels from user_label "
+ "group by user_id) c on c.user_id=a.id";
SchemaItem schemaItem = SqlParser.parse(sql);
// 通过表名找 TableItem
List<TableItem> tableItems = schemaItem.getTableItemAliases().get("user_label".toLowerCase());
tableItems.forEach(tableItem -> Assert.assertEquals("c", tableItem.getAlias()));
TableItem tableItem = tableItems.get(0);
Assert.assertFalse(tableItem.isMain());
Assert.assertTrue(tableItem.isSubQuery());
// 通过字段名找 FieldItem
List<FieldItem> fieldItems = schemaItem.getColumnFields().get(tableItem.getAlias() + ".labels".toLowerCase());
fieldItems.forEach(
fieldItem -> Assert.assertEquals("c.labels", fieldItem.getOwner() + "." + fieldItem.getFieldName()));
// 获取当前表关联条件字段
Map<FieldItem, List<FieldItem>> relationTableFields = tableItem.getRelationTableFields();
relationTableFields.keySet()
.forEach(fieldItem -> Assert.assertEquals("user_id", fieldItem.getColumn().getColumnName()));
// 获取关联字段在select中的对应字段
// List<FieldItem> relationSelectFieldItem =
// tableItem.getRelationKeyFieldItems();
// relationSelectFieldItem.forEach(fieldItem -> Assert.assertEquals("c.labels",
// fieldItem.getOwner() + "." + fieldItem.getColumn().getColumnName()));
}
}
@@ -1,40 +0,0 @@
package com.alibaba.otter.canal.client.adapter.es.test;
import java.sql.SQLException;
import com.alibaba.druid.pool.DruidDataSource;
public class TestConstant {
public final static String jdbcUrl = "jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true";
public final static String jdbcUser = "root";
public final static String jdbcPassword = "121212";
public final static String esHosts = "127.0.0.1:9300";
public final static String clusterName = "elasticsearch";
public final static DruidDataSource dataSource;
static {
dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl(jdbcUrl);
dataSource.setUsername(jdbcUser);
dataSource.setPassword(jdbcPassword);
dataSource.setInitialSize(1);
dataSource.setMinIdle(1);
dataSource.setMaxActive(1);
dataSource.setMaxWait(60000);
dataSource.setTimeBetweenEvictionRunsMillis(60000);
dataSource.setMinEvictableIdleTimeMillis(300000);
dataSource.setPoolPreparedStatements(false);
dataSource.setMaxPoolPreparedStatementPerConnectionSize(20);
dataSource.setValidationQuery("select 1");
try {
dataSource.init();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
@@ -1,68 +0,0 @@
package com.alibaba.otter.canal.client.adapter.es.test.sync;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import com.alibaba.otter.canal.client.adapter.es.ESAdapter;
import com.alibaba.otter.canal.client.adapter.es.test.TestConstant;
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
public class Common {
public static ESAdapter init() {
DatasourceConfig.DATA_SOURCES.put("defaultDS", TestConstant.dataSource);
OuterAdapterConfig outerAdapterConfig = new OuterAdapterConfig();
outerAdapterConfig.setName("es");
outerAdapterConfig.setHosts(TestConstant.esHosts);
Map<String, String> properties = new HashMap<>();
properties.put("cluster.name", TestConstant.clusterName);
outerAdapterConfig.setProperties(properties);
ESAdapter esAdapter = new ESAdapter();
esAdapter.init(outerAdapterConfig, null);
return esAdapter;
}
public static void sqlExe(DataSource dataSource, String sql) {
Connection conn = null;
Statement stmt = null;
try {
conn = dataSource.getConnection();
conn.setAutoCommit(false);
stmt = conn.createStatement();
stmt.execute(sql);
conn.commit();
} catch (Exception e) {
if (conn != null) {
try {
conn.rollback();
} catch (SQLException e1) {
// ignore
}
}
e.printStackTrace();
} finally {
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
// ignore
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
// ignore
}
}
}
}
}
@@ -1,135 +0,0 @@
package com.alibaba.otter.canal.client.adapter.es.test.sync;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.elasticsearch.action.get.GetResponse;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import com.alibaba.otter.canal.client.adapter.es.ESAdapter;
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig;
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
import com.alibaba.otter.canal.client.adapter.support.Dml;
@Ignore
public class LabelSyncJoinSub2Test {
private ESAdapter esAdapter;
@Before
public void init() {
// AdapterConfigs.put("es", "mytest_user_join_sub2.yml");
esAdapter = Common.init();
}
/**
* 带函数子查询从表插入
*/
@Test
public void test01() {
DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS");
Common.sqlExe(ds, "delete from label where id=1 or id=2");
Common.sqlExe(ds, "insert into label (id,user_id,label) values (1,1,'a')");
Common.sqlExe(ds, "insert into label (id,user_id,label) values (2,1,'b')");
Dml dml = new Dml();
dml.setDestination("example");
dml.setTs(new Date().getTime());
dml.setType("INSERT");
dml.setDatabase("mytest");
dml.setTable("label");
List<Map<String, Object>> dataList = new ArrayList<>();
Map<String, Object> data = new LinkedHashMap<>();
dataList.add(data);
data.put("id", 2L);
data.put("user_id", 1L);
data.put("label", "b");
dml.setData(dataList);
String database = dml.getDatabase();
String table = dml.getTable();
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
Assert.assertEquals("b;a_", response.getSource().get("_labels"));
}
/**
* 带函数子查询从表更新
*/
@Test
public void test02() {
DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS");
Common.sqlExe(ds, "update label set label='aa' where id=1");
Dml dml = new Dml();
dml.setDestination("example");
dml.setTs(new Date().getTime());
dml.setType("UPDATE");
dml.setDatabase("mytest");
dml.setTable("label");
List<Map<String, Object>> dataList = new ArrayList<>();
Map<String, Object> data = new LinkedHashMap<>();
dataList.add(data);
data.put("id", 1L);
data.put("user_id", 1L);
data.put("label", "aa");
dml.setData(dataList);
List<Map<String, Object>> oldList = new ArrayList<>();
Map<String, Object> old = new LinkedHashMap<>();
oldList.add(old);
old.put("label", "v");
dml.setOld(oldList);
String database = dml.getDatabase();
String table = dml.getTable();
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
Assert.assertEquals("b;aa_", response.getSource().get("_labels"));
}
/**
* 带函数子查询从表删除
*/
@Test
public void test03() {
DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS");
Common.sqlExe(ds, "delete from label where id=1");
Dml dml = new Dml();
dml.setDestination("example");
dml.setTs(new Date().getTime());
dml.setType("DELETE");
dml.setDatabase("mytest");
dml.setTable("label");
List<Map<String, Object>> dataList = new ArrayList<>();
Map<String, Object> data = new LinkedHashMap<>();
dataList.add(data);
data.put("id", 1L);
data.put("user_id", 1L);
data.put("label", "a");
dml.setData(dataList);
String database = dml.getDatabase();
String table = dml.getTable();
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
Assert.assertEquals("b_", response.getSource().get("_labels"));
}
}
@@ -1,135 +0,0 @@
package com.alibaba.otter.canal.client.adapter.es.test.sync;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.elasticsearch.action.get.GetResponse;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import com.alibaba.otter.canal.client.adapter.es.ESAdapter;
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig;
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
import com.alibaba.otter.canal.client.adapter.support.Dml;
@Ignore
public class LabelSyncJoinSubTest {
private ESAdapter esAdapter;
@Before
public void init() {
// AdapterConfigs.put("es", "mytest_user_join_sub.yml");
esAdapter = Common.init();
}
/**
* 子查询从表插入
*/
@Test
public void test01() {
DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS");
Common.sqlExe(ds, "delete from label where id=1 or id=2");
Common.sqlExe(ds, "insert into label (id,user_id,label) values (1,1,'a')");
Common.sqlExe(ds, "insert into label (id,user_id,label) values (2,1,'b')");
Dml dml = new Dml();
dml.setDestination("example");
dml.setTs(new Date().getTime());
dml.setType("INSERT");
dml.setDatabase("mytest");
dml.setTable("label");
List<Map<String, Object>> dataList = new ArrayList<>();
Map<String, Object> data = new LinkedHashMap<>();
dataList.add(data);
data.put("id", 2L);
data.put("user_id", 1L);
data.put("label", "b");
dml.setData(dataList);
String database = dml.getDatabase();
String table = dml.getTable();
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
Assert.assertEquals("b;a", response.getSource().get("_labels"));
}
/**
* 子查询从表更新
*/
@Test
public void test02() {
DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS");
Common.sqlExe(ds, "update label set label='aa' where id=1");
Dml dml = new Dml();
dml.setDestination("example");
dml.setTs(new Date().getTime());
dml.setType("UPDATE");
dml.setDatabase("mytest");
dml.setTable("label");
List<Map<String, Object>> dataList = new ArrayList<>();
Map<String, Object> data = new LinkedHashMap<>();
dataList.add(data);
data.put("id", 1L);
data.put("user_id", 1L);
data.put("label", "aa");
dml.setData(dataList);
List<Map<String, Object>> oldList = new ArrayList<>();
Map<String, Object> old = new LinkedHashMap<>();
oldList.add(old);
old.put("label", "a");
dml.setOld(oldList);
String database = dml.getDatabase();
String table = dml.getTable();
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
Assert.assertEquals("b;aa", response.getSource().get("_labels"));
}
/**
* 子查询从表删除
*/
@Test
public void test03() {
DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS");
Common.sqlExe(ds, "delete from label where id=1");
Dml dml = new Dml();
dml.setDestination("example");
dml.setTs(new Date().getTime());
dml.setType("DELETE");
dml.setDatabase("mytest");
dml.setTable("label");
List<Map<String, Object>> dataList = new ArrayList<>();
Map<String, Object> data = new LinkedHashMap<>();
dataList.add(data);
data.put("id", 1L);
data.put("user_id", 1L);
data.put("label", "a");
dml.setData(dataList);
String database = dml.getDatabase();
String table = dml.getTable();
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
Assert.assertEquals("b", response.getSource().get("_labels"));
}
}
@@ -1,100 +0,0 @@
package com.alibaba.otter.canal.client.adapter.es.test.sync;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.elasticsearch.action.get.GetResponse;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import com.alibaba.otter.canal.client.adapter.es.ESAdapter;
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig;
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
import com.alibaba.otter.canal.client.adapter.support.Dml;
@Ignore
public class RoleSyncJoinOne2Test {
private ESAdapter esAdapter;
@Before
public void init() {
// AdapterConfigs.put("es", "mytest_user_join_one2.yml");
esAdapter = Common.init();
}
/**
* 带函数非子查询从表插入
*/
@Test
public void test01() {
DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS");
Common.sqlExe(ds, "delete from role where id=1");
Common.sqlExe(ds, "insert into role (id,role_name) values (1,'admin')");
Dml dml = new Dml();
dml.setDestination("example");
dml.setTs(new Date().getTime());
dml.setType("INSERT");
dml.setDatabase("mytest");
dml.setTable("role");
List<Map<String, Object>> dataList = new ArrayList<>();
Map<String, Object> data = new LinkedHashMap<>();
dataList.add(data);
data.put("id", 1L);
data.put("role_name", "admin");
dml.setData(dataList);
String database = dml.getDatabase();
String table = dml.getTable();
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
Assert.assertEquals("admin_", response.getSource().get("_role_name"));
}
/**
* 带函数非子查询从表更新
*/
@Test
public void test02() {
DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS");
Common.sqlExe(ds, "update role set role_name='admin3' where id=1");
Dml dml = new Dml();
dml.setDestination("example");
dml.setTs(new Date().getTime());
dml.setType("UPDATE");
dml.setDatabase("mytest");
dml.setTable("role");
List<Map<String, Object>> dataList = new ArrayList<>();
Map<String, Object> data = new LinkedHashMap<>();
dataList.add(data);
data.put("id", 1L);
data.put("role_name", "admin3");
dml.setData(dataList);
List<Map<String, Object>> oldList = new ArrayList<>();
Map<String, Object> old = new LinkedHashMap<>();
oldList.add(old);
old.put("role_name", "admin");
dml.setOld(oldList);
String database = dml.getDatabase();
String table = dml.getTable();
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
Assert.assertEquals("admin3_", response.getSource().get("_role_name"));
}
}
@@ -1,196 +0,0 @@
package com.alibaba.otter.canal.client.adapter.es.test.sync;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.elasticsearch.action.get.GetResponse;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import com.alibaba.otter.canal.client.adapter.es.ESAdapter;
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig;
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
import com.alibaba.otter.canal.client.adapter.support.Dml;
@Ignore
public class RoleSyncJoinOneTest {
private ESAdapter esAdapter;
@Before
public void init() {
// AdapterConfigs.put("es", "mytest_user_join_one.yml");
esAdapter = Common.init();
}
/**
* 非子查询从表插入
*/
@Test
public void test01() {
DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS");
Common.sqlExe(ds, "delete from role where id=1");
Common.sqlExe(ds, "insert into role (id,role_name) values (1,'admin')");
Dml dml = new Dml();
dml.setDestination("example");
dml.setTs(new Date().getTime());
dml.setType("INSERT");
dml.setDatabase("mytest");
dml.setTable("role");
List<Map<String, Object>> dataList = new ArrayList<>();
Map<String, Object> data = new LinkedHashMap<>();
dataList.add(data);
data.put("id", 1L);
data.put("role_name", "admin");
dml.setData(dataList);
String database = dml.getDatabase();
String table = dml.getTable();
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
Assert.assertEquals("admin", response.getSource().get("_role_name"));
}
/**
* 非子查询从表更新
*/
@Test
public void test02() {
DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS");
Common.sqlExe(ds, "update role set role_name='admin2' where id=1");
Dml dml = new Dml();
dml.setDestination("example");
dml.setTs(new Date().getTime());
dml.setType("UPDATE");
dml.setDatabase("mytest");
dml.setTable("role");
List<Map<String, Object>> dataList = new ArrayList<>();
Map<String, Object> data = new LinkedHashMap<>();
dataList.add(data);
data.put("id", 1L);
data.put("role_name", "admin2");
dml.setData(dataList);
List<Map<String, Object>> oldList = new ArrayList<>();
Map<String, Object> old = new LinkedHashMap<>();
oldList.add(old);
old.put("role_name", "admin");
dml.setOld(oldList);
String database = dml.getDatabase();
String table = dml.getTable();
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
Assert.assertEquals("admin2", response.getSource().get("_role_name"));
}
/**
* 主表更新外键值
*/
@Test
public void test03() {
DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS");
Common.sqlExe(ds, "delete from role where id=2");
Common.sqlExe(ds, "insert into role (id,role_name) values (2,'operator')");
Common.sqlExe(ds, "update user set role_id=2 where id=1");
Dml dml = new Dml();
dml.setDestination("example");
dml.setTs(new Date().getTime());
dml.setType("UPDATE");
dml.setDatabase("mytest");
dml.setTable("user");
List<Map<String, Object>> dataList = new ArrayList<>();
Map<String, Object> data = new LinkedHashMap<>();
dataList.add(data);
data.put("id", 1L);
data.put("role_id", 2L);
dml.setData(dataList);
List<Map<String, Object>> oldList = new ArrayList<>();
Map<String, Object> old = new LinkedHashMap<>();
oldList.add(old);
old.put("role_id", 1L);
dml.setOld(oldList);
String database = dml.getDatabase();
String table = dml.getTable();
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
Assert.assertEquals("operator", response.getSource().get("_role_name"));
Common.sqlExe(ds, "update user set role_id=1 where id=1");
Dml dml2 = new Dml();
dml2.setDestination("example");
dml2.setTs(new Date().getTime());
dml2.setType("UPDATE");
dml2.setDatabase("mytest");
dml2.setTable("user");
List<Map<String, Object>> dataList2 = new ArrayList<>();
Map<String, Object> data2 = new LinkedHashMap<>();
dataList2.add(data2);
data2.put("id", 1L);
data2.put("role_id", 1L);
dml2.setData(dataList2);
List<Map<String, Object>> oldList2 = new ArrayList<>();
Map<String, Object> old2 = new LinkedHashMap<>();
oldList2.add(old2);
old2.put("role_id", 2L);
dml2.setOld(oldList2);
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml2);
GetResponse response2 = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
Assert.assertEquals("admin2", response2.getSource().get("_role_name"));
}
/**
* 非子查询从表删除
*/
@Test
public void test04() {
DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS");
Common.sqlExe(ds, "delete from role where id=1");
Dml dml = new Dml();
dml.setDestination("example");
dml.setTs(new Date().getTime());
dml.setType("DELETE");
dml.setDatabase("mytest");
dml.setTable("role");
List<Map<String, Object>> dataList = new ArrayList<>();
Map<String, Object> data = new LinkedHashMap<>();
dataList.add(data);
data.put("id", 1L);
data.put("role_name", "admin");
dml.setData(dataList);
String database = dml.getDatabase();
String table = dml.getTable();
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
Assert.assertNull(response.getSource().get("_role_name"));
}
}
@@ -1,101 +0,0 @@
package com.alibaba.otter.canal.client.adapter.es.test.sync;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.elasticsearch.action.get.GetResponse;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import com.alibaba.otter.canal.client.adapter.es.ESAdapter;
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig;
import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
import com.alibaba.otter.canal.client.adapter.support.Dml;
@Ignore
public class UserSyncJoinOneTest {
private ESAdapter esAdapter;
@Before
public void init() {
// AdapterConfigs.put("es", "mytest_user_join_one.yml");
esAdapter = Common.init();
}
/**
* 主表带函数插入
*/
@Test
public void test01() {
DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS");
Common.sqlExe(ds, "delete from user where id=1");
Common.sqlExe(ds, "insert into user (id,name,role_id) values (1,'Eric',1)");
Dml dml = new Dml();
dml.setDestination("example");
dml.setTs(new Date().getTime());
dml.setType("INSERT");
dml.setDatabase("mytest");
dml.setTable("user");
List<Map<String, Object>> dataList = new ArrayList<>();
Map<String, Object> data = new LinkedHashMap<>();
dataList.add(data);
data.put("id", 1L);
data.put("name", "Eric");
data.put("role_id", 1L);
data.put("c_time", new Date());
dml.setData(dataList);
String database = dml.getDatabase();
String table = dml.getTable();
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
Assert.assertEquals("Eric_", response.getSource().get("_name"));
}
/**
* 主表带函数更新
*/
@Test
public void test02() {
DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS");
Common.sqlExe(ds, "update user set name='Eric2' where id=1");
Dml dml = new Dml();
dml.setDestination("example");
dml.setTs(new Date().getTime());
dml.setType("UPDATE");
dml.setDatabase("mytest");
dml.setTable("user");
List<Map<String, Object>> dataList = new ArrayList<>();
Map<String, Object> data = new LinkedHashMap<>();
dataList.add(data);
data.put("id", 1L);
data.put("name", "Eric2");
dml.setData(dataList);
List<Map<String, Object>> oldList = new ArrayList<>();
Map<String, Object> old = new LinkedHashMap<>();
oldList.add(old);
old.put("name", "Eric");
dml.setOld(oldList);
String database = dml.getDatabase();
String table = dml.getTable();
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
Assert.assertEquals("Eric2_", response.getSource().get("_name"));
}
}
@@ -1,127 +0,0 @@
package com.alibaba.otter.canal.client.adapter.es.test.sync;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.elasticsearch.action.get.GetResponse;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import com.alibaba.otter.canal.client.adapter.es.ESAdapter;
import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig;
import com.alibaba.otter.canal.client.adapter.support.Dml;
@Ignore
public class UserSyncSingleTest {
private ESAdapter esAdapter;
@Before
public void init() {
// AdapterConfigs.put("es", "mytest_user_single.yml");
esAdapter = Common.init();
}
/**
* 单表插入
*/
@Test
public void test01() {
Dml dml = new Dml();
dml.setDestination("example");
dml.setTs(new Date().getTime());
dml.setType("INSERT");
dml.setDatabase("mytest");
dml.setTable("user");
List<Map<String, Object>> dataList = new ArrayList<>();
Map<String, Object> data = new LinkedHashMap<>();
dataList.add(data);
data.put("id", 1L);
data.put("name", "Eric");
data.put("role_id", 1L);
data.put("c_time", new Date());
dml.setData(dataList);
String database = dml.getDatabase();
String table = dml.getTable();
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
Assert.assertEquals("Eric", response.getSource().get("_name"));
}
/**
* 单表更新
*/
@Test
public void test02() {
Dml dml = new Dml();
dml.setDestination("example");
dml.setTs(new Date().getTime());
dml.setType("UPDATE");
dml.setDatabase("mytest");
dml.setTable("user");
List<Map<String, Object>> dataList = new ArrayList<>();
Map<String, Object> data = new LinkedHashMap<>();
dataList.add(data);
data.put("id", 1L);
data.put("name", "Eric2");
dml.setData(dataList);
List<Map<String, Object>> oldList = new ArrayList<>();
Map<String, Object> old = new LinkedHashMap<>();
oldList.add(old);
old.put("name", "Eric");
dml.setOld(oldList);
String database = dml.getDatabase();
String table = dml.getTable();
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
Assert.assertEquals("Eric2", response.getSource().get("_name"));
}
/**
* 单表删除
*/
@Test
public void test03() {
Dml dml = new Dml();
dml.setDestination("example");
dml.setTs(new Date().getTime());
dml.setType("DELETE");
dml.setDatabase("mytest");
dml.setTable("user");
List<Map<String, Object>> dataList = new ArrayList<>();
Map<String, Object> data = new LinkedHashMap<>();
dataList.add(data);
data.put("id", 1L);
data.put("name", "Eric");
data.put("role_id", 1L);
data.put("c_time", new Date());
dml.setData(dataList);
String database = dml.getDatabase();
String table = dml.getTable();
Map<String, ESSyncConfig> esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table);
esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml);
GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get();
Assert.assertNull(response.getSource());
}
// @After
// public void after() {
// esAdapter.destroy();
// DatasourceConfig.DATA_SOURCES.values().forEach(DruidDataSource::close);
// }
}
@@ -1,39 +0,0 @@
-- ----------------------------
-- Table structure for label
-- ----------------------------
DROP TABLE IF EXISTS `label`;
CREATE TABLE `label` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) NOT NULL,
`label` varchar(30) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`role_name` varchar(30) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(30) NOT NULL,
`c_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`role_id` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
insert into user (id,name,role_id) values (1,'Eric',1);
insert into role (id,role_name) values (1,'admin');
insert into role (id,role_name) values (2,'operator');
insert into label (id,user_id,label) values (1,1,'a');
insert into label (id,user_id,label) values (2,1,'b');
commit;
@@ -1,21 +0,0 @@
{
"_doc": {
"properties": {
"_name": {
"type": "text"
},
"_role_id": {
"type": "long"
},
"_role_name": {
"type": "text"
},
"_labels": {
"type": "text"
},
"_c_time": {
"type": "date"
}
}
}
}
@@ -1,8 +0,0 @@
dataSourceKey: defaultDS
destination: example
esMapping:
_index: mytest_user
_type: _doc
_id: _id
sql: "select a.id as _id, a.name as _name, a.role_id as _role_id, a.c_time as _c_time from user a"
commitBatch: 3000
@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="ERROR">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
@@ -1,13 +0,0 @@
<configuration scan="true" scanPeriod=" 5 seconds">
<jmxConfigurator />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n
</pattern>
</encoder>
</appender>
<root level="TRACE">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
-86
View File
@@ -1,86 +0,0 @@
<?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>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.4-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.alibaba.otter</groupId>
<artifactId>client-adapter.hbase</artifactId>
<packaging>jar</packaging>
<name>canal client adapter hbase module for otter ${project.version}</name>
<dependencies>
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>client-adapter.common</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>1.1.2</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>jdk.tools</groupId>
<artifactId>jdk.tools</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>12.0.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.4</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<copy todir="${project.basedir}/../launcher/target/classes/hbase" overwrite="true">
<fileset dir="${project.basedir}/target/classes/hbase" erroronmissingdir="true">
<include name="*.yml" />
</fileset>
</copy>
</tasks>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -1,226 +0,0 @@
package com.alibaba.otter.canal.client.adapter.hbase;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.HTable;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.filter.FirstKeyOnlyFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfigLoader;
import com.alibaba.otter.canal.client.adapter.hbase.monitor.HbaseConfigMonitor;
import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseEtlService;
import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseSyncService;
import com.alibaba.otter.canal.client.adapter.hbase.support.HbaseTemplate;
import com.alibaba.otter.canal.client.adapter.support.Dml;
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
import com.alibaba.otter.canal.client.adapter.support.SPI;
/**
* HBase外部适配器
*
* @author machengyuan 2018-8-21 下午8:45:38
* @version 1.0.0
*/
@SPI("hbase")
public class HbaseAdapter implements OuterAdapter {
private static Logger logger = LoggerFactory.getLogger(HbaseAdapter.class);
private Map<String, MappingConfig> hbaseMapping = new ConcurrentHashMap<>(); // 文件名对应配置
private Map<String, Map<String, MappingConfig>> mappingConfigCache = new ConcurrentHashMap<>(); // 库名-表名对应配置
private HbaseSyncService hbaseSyncService;
private HbaseTemplate hbaseTemplate;
private HbaseConfigMonitor configMonitor;
private Properties envProperties;
public Map<String, MappingConfig> getHbaseMapping() {
return hbaseMapping;
}
public Map<String, Map<String, MappingConfig>> getMappingConfigCache() {
return mappingConfigCache;
}
@Override
public void init(OuterAdapterConfig configuration, Properties envProperties) {
try {
this.envProperties = envProperties;
Map<String, MappingConfig> hbaseMappingTmp = MappingConfigLoader.load(envProperties);
// 过滤不匹配的key的配置
hbaseMappingTmp.forEach((key, mappingConfig) -> {
if ((mappingConfig.getOuterAdapterKey() == null && configuration.getKey() == null)
|| (mappingConfig.getOuterAdapterKey() != null
&& mappingConfig.getOuterAdapterKey().equalsIgnoreCase(configuration.getKey()))) {
hbaseMapping.put(key, mappingConfig);
}
});
for (Map.Entry<String, MappingConfig> entry : hbaseMapping.entrySet()) {
String configName = entry.getKey();
MappingConfig mappingConfig = entry.getValue();
String k;
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
k = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "-"
+ StringUtils.trimToEmpty(mappingConfig.getGroupId()) + "_"
+ mappingConfig.getHbaseMapping().getDatabase() + "-"
+ mappingConfig.getHbaseMapping().getTable();
} else {
k = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_"
+ mappingConfig.getHbaseMapping().getDatabase() + "-"
+ mappingConfig.getHbaseMapping().getTable();
}
Map<String, MappingConfig> configMap = mappingConfigCache.computeIfAbsent(k,
k1 -> new ConcurrentHashMap<>());
configMap.put(configName, mappingConfig);
}
Map<String, String> properties = configuration.getProperties();
Configuration hbaseConfig = HBaseConfiguration.create();
properties.forEach(hbaseConfig::set);
hbaseTemplate = new HbaseTemplate(hbaseConfig);
hbaseSyncService = new HbaseSyncService(hbaseTemplate);
configMonitor = new HbaseConfigMonitor();
configMonitor.init(this, envProperties);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void sync(List<Dml> dmls) {
if (dmls == null || dmls.isEmpty()) {
return;
}
for (Dml dml : dmls) {
sync(dml);
}
}
private void sync(Dml dml) {
if (dml == null) {
return;
}
String destination = StringUtils.trimToEmpty(dml.getDestination());
String groupId = StringUtils.trimToEmpty(dml.getGroupId());
String database = dml.getDatabase();
String table = dml.getTable();
Map<String, MappingConfig> configMap;
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
configMap = mappingConfigCache.get(destination + "-" + groupId + "_" + database + "-" + table);
} else {
configMap = mappingConfigCache.get(destination + "_" + database + "-" + table);
}
if (configMap != null) {
List<MappingConfig> configs = new ArrayList<>();
configMap.values().forEach(config -> {
if (StringUtils.isNotEmpty(config.getGroupId())) {
if (config.getGroupId().equals(dml.getGroupId())) {
configs.add(config);
}
} else {
configs.add(config);
}
});
if (!configs.isEmpty()) {
configs.forEach(config -> hbaseSyncService.sync(config, dml));
}
}
}
@Override
public EtlResult etl(String task, List<String> params) {
EtlResult etlResult = new EtlResult();
MappingConfig config = hbaseMapping.get(task);
HbaseEtlService hbaseEtlService = new HbaseEtlService(hbaseTemplate, config);
if (config != null) {
return hbaseEtlService.importData(params);
} else {
StringBuilder resultMsg = new StringBuilder();
boolean resSucc = true;
for (MappingConfig configTmp : hbaseMapping.values()) {
// 取所有的destination为task的配置
if (configTmp.getDestination().equals(task)) {
EtlResult etlRes = hbaseEtlService.importData(params);
if (!etlRes.getSucceeded()) {
resSucc = false;
resultMsg.append(etlRes.getErrorMessage()).append("\n");
} else {
resultMsg.append(etlRes.getResultMessage()).append("\n");
}
}
}
if (resultMsg.length() > 0) {
etlResult.setSucceeded(resSucc);
if (resSucc) {
etlResult.setResultMessage(resultMsg.toString());
} else {
etlResult.setErrorMessage(resultMsg.toString());
}
return etlResult;
}
}
etlResult.setSucceeded(false);
etlResult.setErrorMessage("Task not found");
return etlResult;
}
@Override
public Map<String, Object> count(String task) {
MappingConfig config = hbaseMapping.get(task);
String hbaseTable = config.getHbaseMapping().getHbaseTable();
long rowCount = 0L;
try {
HTable table = (HTable) hbaseTemplate.getConnection().getTable(TableName.valueOf(hbaseTable));
Scan scan = new Scan();
scan.setFilter(new FirstKeyOnlyFilter());
ResultScanner resultScanner = table.getScanner(scan);
for (Result result : resultScanner) {
rowCount += result.size();
}
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
Map<String, Object> res = new LinkedHashMap<>();
res.put("hbaseTable", hbaseTable);
res.put("count", rowCount);
return res;
}
@Override
public void destroy() {
if (configMonitor != null) {
configMonitor.destroy();
}
try {
hbaseTemplate.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public String getDestination(String task) {
MappingConfig config = hbaseMapping.get(task);
if (config != null && config.getHbaseMapping() != null) {
return config.getDestination();
}
return null;
}
}
@@ -1,410 +0,0 @@
package com.alibaba.otter.canal.client.adapter.hbase.config;
import com.alibaba.otter.canal.client.adapter.support.AdapterConfig;
import java.util.*;
/**
* HBase表映射配置
*
* @author rewerma 2018-8-21 下午06:45:49
* @version 1.0.0
*/
public class MappingConfig implements AdapterConfig {
private String dataSourceKey; // 数据源key
private String outerAdapterKey; // adapter key
private String groupId; // groupId
private String destination; // canal实例或MQ的topic
private HbaseMapping hbaseMapping; // hbase映射配置
public String getDataSourceKey() {
return dataSourceKey;
}
public void setDataSourceKey(String dataSourceKey) {
this.dataSourceKey = dataSourceKey;
}
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public String getOuterAdapterKey() {
return outerAdapterKey;
}
public void setOuterAdapterKey(String outerAdapterKey) {
this.outerAdapterKey = outerAdapterKey;
}
public String getDestination() {
return destination;
}
public void setDestination(String destination) {
this.destination = destination;
}
public HbaseMapping getHbaseMapping() {
return hbaseMapping;
}
public void setHbaseMapping(HbaseMapping hbaseMapping) {
this.hbaseMapping = hbaseMapping;
}
public AdapterMapping getMapping() {
return hbaseMapping;
}
public void validate() {
if (hbaseMapping.database == null || hbaseMapping.database.isEmpty()) {
throw new NullPointerException("hbaseMapping.database");
}
if (hbaseMapping.table == null || hbaseMapping.table.isEmpty()) {
throw new NullPointerException("hbaseMapping.table");
}
if (hbaseMapping.hbaseTable == null || hbaseMapping.hbaseTable.isEmpty()) {
throw new NullPointerException("hbaseMapping.hbaseTable");
}
if (hbaseMapping.mode == null) {
throw new NullPointerException("hbaseMapping.mode");
}
if (hbaseMapping.rowKey != null && hbaseMapping.rowKeyColumn != null) {
throw new RuntimeException("已配置了复合主键作为RowKey,无需再指定RowKey列");
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MappingConfig config = (MappingConfig) o;
return hbaseMapping != null ? hbaseMapping.equals(config.hbaseMapping) : config.hbaseMapping == null;
}
@Override
public int hashCode() {
return hbaseMapping != null ? hbaseMapping.hashCode() : 0;
}
public static class ColumnItem {
private boolean isRowKey = false;
private Integer rowKeyLen;
private String column;
private String family;
private String qualifier;
private String type;
public boolean isRowKey() {
return isRowKey;
}
public void setRowKey(boolean rowKey) {
isRowKey = rowKey;
}
public Integer getRowKeyLen() {
return rowKeyLen;
}
public void setRowKeyLen(Integer rowKeyLen) {
this.rowKeyLen = rowKeyLen;
}
public String getColumn() {
return column;
}
public void setColumn(String column) {
this.column = column;
}
public String getFamily() {
return family;
}
public void setFamily(String family) {
this.family = family;
}
public String getQualifier() {
return qualifier;
}
public void setQualifier(String qualifier) {
this.qualifier = qualifier;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ColumnItem that = (ColumnItem) o;
return Objects.equals(column, that.column);
}
@Override
public int hashCode() {
return Objects.hash(column);
}
}
public enum Mode {
STRING("STRING"), NATIVE("NATIVE"), PHOENIX("PHOENIX");
private String type;
public String getType() {
return type;
}
Mode(String type){
this.type = type;
}
}
public static class HbaseMapping implements AdapterMapping {
private Mode mode = Mode.STRING; // hbase默认转换格式
private String database; // 数据库名或schema名
private String table; // 表面名
private String hbaseTable; // hbase表名
private String family = "CF"; // 默认统一column family
private boolean uppercaseQualifier = true; // 是否转大写
private boolean autoCreateTable = false; // 同步时HBase中表不存在的情况下自动建表
private String rowKey; // 指定复合主键为rowKey
private Map<String, String> columns; // 字段映射
private List<String> excludeColumns; // 不映射的字段
private ColumnItem rowKeyColumn; // rowKey字段
private String etlCondition; // etl条件sql
private Map<String, ColumnItem> columnItems = new LinkedHashMap<>(); // 转换后的字段映射列表
private Set<String> families = new LinkedHashSet<>(); // column family列表
private int readBatch = 5000;
private int commitBatch = 5000; // etl等批量提交大小
public Mode getMode() {
return mode;
}
public void setMode(Mode mode) {
this.mode = mode;
}
public String getDatabase() {
return database;
}
public void setDatabase(String database) {
this.database = database;
}
public String getTable() {
return table;
}
public void setTable(String table) {
this.table = table;
}
public String getHbaseTable() {
return hbaseTable;
}
public void setHbaseTable(String hbaseTable) {
this.hbaseTable = hbaseTable;
}
public Map<String, String> getColumns() {
return columns;
}
public boolean isAutoCreateTable() {
return autoCreateTable;
}
public void setAutoCreateTable(boolean autoCreateTable) {
this.autoCreateTable = autoCreateTable;
}
public int getReadBatch() {
return readBatch;
}
public void setReadBatch(int readBatch) {
this.readBatch = readBatch;
}
public int getCommitBatch() {
return commitBatch;
}
public void setCommitBatch(int commitBatch) {
this.commitBatch = commitBatch;
}
public String getRowKey() {
return rowKey;
}
public void setRowKey(String rowKey) {
this.rowKey = rowKey;
}
public String getEtlCondition() {
return etlCondition;
}
public void setEtlCondition(String etlCondition) {
this.etlCondition = etlCondition;
}
public void setColumns(Map<String, String> columns) {
this.columns = columns;
if (columns != null) {
for (Map.Entry<String, String> columnField : columns.entrySet()) {
String field = columnField.getValue();
String type = null;
if (field != null) {
// 解析类型
int i = field.indexOf("$");
if (i > -1) {
type = field.substring(i + 1);
field = field.substring(0, i);
}
}
ColumnItem columnItem = new ColumnItem();
columnItem.setColumn(columnField.getKey());
columnItem.setType(type);
if (field != null && field.toUpperCase().startsWith("ROWKEY")) {
int idx = field.toUpperCase().indexOf("LEN:");
if (idx > -1) {
String len = field.substring(idx + 4);
try {
columnItem.setRowKeyLen(Integer.parseInt(len));
} catch (Exception e) {
// ignore
}
}
columnItem.setRowKey(true);
rowKeyColumn = columnItem;
} else {
if (field == null || field.equals("")) {
columnItem.setFamily(family);
columnItem.setQualifier(columnField.getKey());
} else {
int len = field.indexOf(":");
if (len > -1) {
columnItem.setFamily(field.substring(0, len));
columnItem.setQualifier(field.substring(len + 1));
} else {
columnItem.setFamily(family);
columnItem.setQualifier(field);
}
}
if (uppercaseQualifier) {
columnItem.setQualifier(columnItem.getQualifier().toUpperCase());
}
families.add(columnItem.getFamily());
}
columnItems.put(columnField.getKey(), columnItem);
}
} else {
this.columns = new LinkedHashMap<>();
}
}
public List<String> getExcludeColumns() {
return excludeColumns;
}
public void setExcludeColumns(List<String> excludeColumns) {
this.excludeColumns = excludeColumns;
}
public String getFamily() {
return family;
}
public void setFamily(String family) {
this.family = family;
if (family == null) {
this.family = "CF";
}
}
public boolean isUppercaseQualifier() {
return uppercaseQualifier;
}
public void setUppercaseQualifier(boolean uppercaseQualifier) {
this.uppercaseQualifier = uppercaseQualifier;
}
public ColumnItem getRowKeyColumn() {
return rowKeyColumn;
}
public void setRowKeyColumn(ColumnItem rowKeyColumn) {
this.rowKeyColumn = rowKeyColumn;
}
public Map<String, ColumnItem> getColumnItems() {
return columnItems;
}
public void setColumnItems(Map<String, ColumnItem> columnItems) {
this.columnItems = columnItems;
}
public Set<String> getFamilies() {
return families;
}
public void setFamilies(Set<String> families) {
this.families = families;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HbaseMapping hbaseMapping = (HbaseMapping) o;
if (table != null ? !table.equals(hbaseMapping.table) : hbaseMapping.table != null) return false;
return hbaseTable != null ? hbaseTable.equals(hbaseMapping.hbaseTable) : hbaseMapping.hbaseTable == null;
}
@Override
public int hashCode() {
int result = table != null ? table.hashCode() : 0;
result = 31 * result + (hbaseTable != null ? hbaseTable.hashCode() : 0);
return result;
}
}
}
@@ -1,51 +0,0 @@
package com.alibaba.otter.canal.client.adapter.hbase.config;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.client.adapter.config.YmlConfigBinder;
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
/**
* HBase表映射配置加载器
*
* @author rewerma 2018-8-21 下午06:45:49
* @version 1.0.0
*/
public class MappingConfigLoader {
private static Logger logger = LoggerFactory.getLogger(MappingConfigLoader.class);
/**
* 加载HBase表映射配置
*
* @return 配置名/配置文件名--对象
*/
public static Map<String, MappingConfig> load(Properties envProperties) {
logger.info("## Start loading hbase mapping config ... ");
Map<String, MappingConfig> result = new LinkedHashMap<>();
Map<String, String> configContentMap = MappingConfigsLoader.loadConfigs("hbase");
configContentMap.forEach((fileName, content) -> {
MappingConfig config = YmlConfigBinder
.bindYmlToObj(null, content, MappingConfig.class, null, envProperties);
if (config == null) {
return;
}
try {
config.validate();
} catch (Exception e) {
throw new RuntimeException("ERROR load Config: " + fileName + " " + e.getMessage(), e);
}
result.put(fileName, config);
});
logger.info("## Hbase mapping config loaded");
return result;
}
}
@@ -1,145 +0,0 @@
package com.alibaba.otter.canal.client.adapter.hbase.monitor;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.client.adapter.config.YmlConfigBinder;
import com.alibaba.otter.canal.client.adapter.hbase.HbaseAdapter;
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
import com.alibaba.otter.canal.client.adapter.support.Util;
public class HbaseConfigMonitor {
private static final Logger logger = LoggerFactory.getLogger(HbaseConfigMonitor.class);
private static final String adapterName = "hbase";
private HbaseAdapter hbaseAdapter;
private Properties envProperties;
private FileAlterationMonitor fileMonitor;
public void init(HbaseAdapter hbaseAdapter, Properties envProperties) {
this.hbaseAdapter = hbaseAdapter;
this.envProperties = envProperties;
File confDir = Util.getConfDirPath(adapterName);
try {
FileAlterationObserver observer = new FileAlterationObserver(confDir,
FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml")));
FileListener listener = new FileListener();
observer.addListener(listener);
fileMonitor = new FileAlterationMonitor(3000, observer);
fileMonitor.start();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
public void destroy() {
try {
fileMonitor.stop();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
private class FileListener extends FileAlterationListenerAdaptor {
@Override
public void onFileCreate(File file) {
super.onFileCreate(file);
try {
// 加载新增的配置文件
String configContent = MappingConfigsLoader.loadConfig(adapterName + File.separator + file.getName());
MappingConfig config = YmlConfigBinder
.bindYmlToObj(null, configContent, MappingConfig.class, null, envProperties);
if (config == null) {
return;
}
config.validate();
addConfigToCache(file, config);
logger.info("Add a new hbase mapping config: {} to canal adapter", file.getName());
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
@Override
public void onFileChange(File file) {
super.onFileChange(file);
try {
if (hbaseAdapter.getHbaseMapping().containsKey(file.getName())) {
// 加载配置文件
String configContent = MappingConfigsLoader
.loadConfig(adapterName + File.separator + file.getName());
if (configContent == null) {
onFileDelete(file);
return;
}
MappingConfig config = YmlConfigBinder
.bindYmlToObj(null, configContent, MappingConfig.class, null, envProperties);
if (config == null) {
return;
}
config.validate();
if (hbaseAdapter.getHbaseMapping().containsKey(file.getName())) {
deleteConfigFromCache(file);
}
addConfigToCache(file, config);
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
@Override
public void onFileDelete(File file) {
super.onFileDelete(file);
try {
if (hbaseAdapter.getHbaseMapping().containsKey(file.getName())) {
deleteConfigFromCache(file);
logger.info("Delete a hbase mapping config: {} of canal adapter", file.getName());
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
private void addConfigToCache(File file, MappingConfig config) {
hbaseAdapter.getHbaseMapping().put(file.getName(), config);
Map<String, MappingConfig> configMap = hbaseAdapter.getMappingConfigCache()
.computeIfAbsent(StringUtils.trimToEmpty(config.getDestination()) + "."
+ config.getHbaseMapping().getDatabase() + "." + config.getHbaseMapping().getTable(),
k1 -> new HashMap<>());
configMap.put(file.getName(), config);
}
private void deleteConfigFromCache(File file) {
hbaseAdapter.getHbaseMapping().remove(file.getName());
for (Map<String, MappingConfig> configMap : hbaseAdapter.getMappingConfigCache().values()) {
if (configMap != null) {
configMap.remove(file.getName());
}
}
}
}
}
@@ -1,281 +0,0 @@
package com.alibaba.otter.canal.client.adapter.hbase.service;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicLong;
import javax.sql.DataSource;
import com.alibaba.otter.canal.client.adapter.support.*;
import org.apache.hadoop.hbase.util.Bytes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.hbase.support.*;
import com.google.common.base.Joiner;
/**
* HBase ETL 操作业务类
*
* @author rewerma @ 2018-10-20
* @version 1.0.0
*/
public class HbaseEtlService extends AbstractEtlService {
private HbaseTemplate hbaseTemplate;
private MappingConfig config;
public HbaseEtlService(HbaseTemplate hbaseTemplate, MappingConfig config){
super("HBase", config);
this.hbaseTemplate = hbaseTemplate;
this.config = config;
}
/**
* 建表
*/
private void createTable() {
try {
// 判断hbase表是否存在,不存在则建表
MappingConfig.HbaseMapping hbaseMapping = config.getHbaseMapping();
if (!hbaseTemplate.tableExists(hbaseMapping.getHbaseTable())) {
hbaseTemplate.createTable(hbaseMapping.getHbaseTable(), hbaseMapping.getFamily());
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
throw new RuntimeException(e);
}
}
/**
* 导入数据
*
* @param params 筛选条件
* @return 导入结果
*/
public EtlResult importData(List<String> params) {
EtlResult etlResult = new EtlResult();
List<String> errMsg = new ArrayList<>();
try {
MappingConfig.HbaseMapping hbaseMapping = config.getHbaseMapping();
if (params != null && params.size() == 1 && "rebuild".equalsIgnoreCase(params.get(0))) {
logger.info(hbaseMapping.getHbaseTable() + " rebuild is starting!");
// 如果表存在则删除
if (hbaseTemplate.tableExists(hbaseMapping.getHbaseTable())) {
hbaseTemplate.disableTable(hbaseMapping.getHbaseTable());
hbaseTemplate.deleteTable(hbaseMapping.getHbaseTable());
}
params = null;
} else {
logger.info(hbaseMapping.getHbaseTable() + " etl is starting!");
}
createTable();
// 拼接sql
String sql = "SELECT * FROM " + config.getHbaseMapping().getDatabase() + "." + hbaseMapping.getTable();
return super.importData(sql, params);
} catch (Exception e) {
logger.error(e.getMessage(), e);
errMsg.add("HBase etl error ==>" + e.getMessage());
}
etlResult.setErrorMessage(Joiner.on("\n").join(errMsg));
return etlResult;
}
/**
* 执行导入
*/
protected boolean executeSqlImport(DataSource ds, String sql, List<Object> values,
AdapterConfig.AdapterMapping mapping, AtomicLong impCount, List<String> errMsg) {
MappingConfig.HbaseMapping hbaseMapping = (MappingConfig.HbaseMapping) mapping;
try {
Util.sqlRS(ds, sql, values, rs -> {
int i = 1;
try {
boolean complete = false;
List<HRow> rows = new ArrayList<>();
String[] rowKeyColumns = null;
if (hbaseMapping.getRowKey() != null) {
rowKeyColumns = hbaseMapping.getRowKey().trim().split(",");
}
while (rs.next()) {
int cc = rs.getMetaData().getColumnCount();
int[] jdbcTypes = new int[cc];
Class<?>[] classes = new Class[cc];
for (int j = 1; j <= cc; j++) {
int jdbcType = rs.getMetaData().getColumnType(j);
jdbcTypes[j - 1] = jdbcType;
classes[j - 1] = JdbcTypeUtil.jdbcType2javaType(jdbcType);
}
HRow row = new HRow();
if (rowKeyColumns != null) {
// 取rowKey字段拼接
StringBuilder rowKeyVale = new StringBuilder();
for (String rowKeyColumnName : rowKeyColumns) {
Object obj = rs.getObject(rowKeyColumnName);
if (obj != null) {
rowKeyVale.append(obj.toString());
}
rowKeyVale.append("|");
}
int len = rowKeyVale.length();
if (len > 0) {
rowKeyVale.delete(len - 1, len);
}
row.setRowKey(Bytes.toBytes(rowKeyVale.toString()));
}
for (int j = 1; j <= cc; j++) {
String columnName = rs.getMetaData().getColumnName(j);
Object val = JdbcTypeUtil.getRSData(rs, columnName, jdbcTypes[j - 1]);
if (val == null) {
continue;
}
MappingConfig.ColumnItem columnItem = hbaseMapping.getColumnItems().get(columnName);
// 没有配置映射
if (columnItem == null) {
String family = hbaseMapping.getFamily();
String qualifile = columnName;
if (hbaseMapping.isUppercaseQualifier()) {
qualifile = qualifile.toUpperCase();
}
if (MappingConfig.Mode.STRING == hbaseMapping.getMode()) {
if (hbaseMapping.getRowKey() == null && j == 1) {
row.setRowKey(Bytes.toBytes(val.toString()));
} else {
row.addCell(family, qualifile, Bytes.toBytes(val.toString()));
}
} else if (MappingConfig.Mode.NATIVE == hbaseMapping.getMode()) {
Type type = Type.getType(classes[j - 1]);
if (hbaseMapping.getRowKey() == null && j == 1) {
row.setRowKey(TypeUtil.toBytes(val, type));
} else {
row.addCell(family, qualifile, TypeUtil.toBytes(val, type));
}
} else if (MappingConfig.Mode.PHOENIX == hbaseMapping.getMode()) {
PhType phType = PhType.getType(classes[j - 1]);
if (hbaseMapping.getRowKey() == null && j == 1) {
row.setRowKey(PhTypeUtil.toBytes(val, phType));
} else {
row.addCell(family, qualifile, PhTypeUtil.toBytes(val, phType));
}
}
} else {
// 如果不需要类型转换
if (columnItem.getType() == null || "".equals(columnItem.getType())) {
if (val instanceof java.sql.Date) {
SimpleDateFormat dateFmt = new SimpleDateFormat("yyyy-MM-dd");
val = dateFmt.format((Date) val);
} else if (val instanceof Timestamp) {
SimpleDateFormat datetimeFmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
val = datetimeFmt.format((Date) val);
}
byte[] valBytes = Bytes.toBytes(val.toString());
if (columnItem.isRowKey()) {
if (columnItem.getRowKeyLen() != null) {
valBytes = Bytes.toBytes(limitLenNum(columnItem.getRowKeyLen(), val));
row.setRowKey(valBytes);
} else {
row.setRowKey(valBytes);
}
} else {
row.addCell(columnItem.getFamily(), columnItem.getQualifier(), valBytes);
}
} else {
if (MappingConfig.Mode.STRING == hbaseMapping.getMode()) {
byte[] valBytes = Bytes.toBytes(val.toString());
if (columnItem.isRowKey()) {
if (columnItem.getRowKeyLen() != null) {
valBytes = Bytes.toBytes(limitLenNum(columnItem.getRowKeyLen(), val));
}
row.setRowKey(valBytes);
} else {
row.addCell(columnItem.getFamily(), columnItem.getQualifier(), valBytes);
}
} else if (MappingConfig.Mode.NATIVE == hbaseMapping.getMode()) {
Type type = Type.getType(columnItem.getType());
if (columnItem.isRowKey()) {
if (columnItem.getRowKeyLen() != null) {
String v = limitLenNum(columnItem.getRowKeyLen(), val);
row.setRowKey(Bytes.toBytes(v));
} else {
row.setRowKey(TypeUtil.toBytes(val, type));
}
} else {
row.addCell(columnItem.getFamily(),
columnItem.getQualifier(),
TypeUtil.toBytes(val, type));
}
} else if (MappingConfig.Mode.PHOENIX == hbaseMapping.getMode()) {
PhType phType = PhType.getType(columnItem.getType());
if (columnItem.isRowKey()) {
row.setRowKey(PhTypeUtil.toBytes(val, phType));
} else {
row.addCell(columnItem.getFamily(),
columnItem.getQualifier(),
PhTypeUtil.toBytes(val, phType));
}
}
}
}
}
if (row.getRowKey() == null) throw new RuntimeException("RowKey 值为空");
rows.add(row);
complete = false;
if (i % hbaseMapping.getCommitBatch() == 0 && !rows.isEmpty()) {
hbaseTemplate.puts(hbaseMapping.getHbaseTable(), rows);
rows.clear();
complete = true;
}
i++;
impCount.incrementAndGet();
if (logger.isDebugEnabled()) {
logger.debug("successful import count:" + impCount.get());
}
}
if (!complete && !rows.isEmpty()) {
hbaseTemplate.puts(hbaseMapping.getHbaseTable(), rows);
}
} catch (Exception e) {
logger.error(hbaseMapping.getHbaseTable() + " etl failed! ==>" + e.getMessage(), e);
errMsg.add(hbaseMapping.getHbaseTable() + " etl failed! ==>" + e.getMessage());
// throw new RuntimeException(e);
}
return i;
});
return true;
} catch (Exception e) {
logger.error(e.getMessage(), e);
return false;
}
}
private static String limitLenNum(int len, Object val) {
if (val == null) {
return null;
}
if (val instanceof Number) {
return String.format("%0" + len + "d", (Number) ((Number) val).longValue());
} else if (val instanceof String) {
return String.format("%0" + len + "d", Long.parseLong((String) val));
}
return null;
}
}
@@ -1,455 +0,0 @@
package com.alibaba.otter.canal.client.adapter.hbase.service;
import java.util.*;
import org.apache.hadoop.hbase.util.Bytes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.hbase.support.*;
import com.alibaba.otter.canal.client.adapter.support.Dml;
/**
* HBase同步操作业务
*
* @author rewerma 2018-8-21 下午06:45:49
* @version 1.0.0
*/
public class HbaseSyncService {
private static Logger logger = LoggerFactory.getLogger(HbaseSyncService.class);
private HbaseTemplate hbaseTemplate; // HBase操作模板
public HbaseSyncService(HbaseTemplate hbaseTemplate){
this.hbaseTemplate = hbaseTemplate;
}
public void sync(MappingConfig config, Dml dml) {
if (config != null) {
String type = dml.getType();
if (type != null && type.equalsIgnoreCase("INSERT")) {
insert(config, dml);
} else if (type != null && type.equalsIgnoreCase("UPDATE")) {
update(config, dml);
} else if (type != null && type.equalsIgnoreCase("DELETE")) {
delete(config, dml);
}
if (logger.isDebugEnabled()) {
logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
}
}
}
/**
* 插入操作
*
* @param config 配置项
* @param dml DML数据
*/
private void insert(MappingConfig config, Dml dml) {
List<Map<String, Object>> data = dml.getData();
if (data == null || data.isEmpty()) {
return;
}
MappingConfig.HbaseMapping hbaseMapping = config.getHbaseMapping();
// if (!validHTable(config)) {
// logger.error("HBase table '{}' not exists",
// hbaseMapping.getHbaseTable());
// return;
// }
int i = 1;
boolean complete = false;
List<HRow> rows = new ArrayList<>();
for (Map<String, Object> r : data) {
HRow hRow = new HRow();
// 拼接复合rowKey
if (hbaseMapping.getRowKey() != null) {
String[] rowKeyColumns = hbaseMapping.getRowKey().trim().split(",");
String rowKeyVale = getRowKeys(rowKeyColumns, r);
// params.put("rowKey", Bytes.toBytes(rowKeyVale));
hRow.setRowKey(Bytes.toBytes(rowKeyVale));
}
convertData2Row(hbaseMapping, hRow, r);
if (hRow.getRowKey() == null) {
throw new RuntimeException("empty rowKey");
}
rows.add(hRow);
complete = false;
if (i % config.getHbaseMapping().getCommitBatch() == 0 && !rows.isEmpty()) {
hbaseTemplate.puts(hbaseMapping.getHbaseTable(), rows);
rows.clear();
complete = true;
}
i++;
}
if (!complete && !rows.isEmpty()) {
hbaseTemplate.puts(hbaseMapping.getHbaseTable(), rows);
}
}
/**
* 将Map数据转换为HRow行数据
*
* @param hbaseMapping hbase映射配置
* @param hRow 行对象
* @param data Map数据
*/
private static void convertData2Row(MappingConfig.HbaseMapping hbaseMapping, HRow hRow, Map<String, Object> data) {
Map<String, MappingConfig.ColumnItem> columnItems = hbaseMapping.getColumnItems();
int i = 0;
for (Map.Entry<String, Object> entry : data.entrySet()) {
if (hbaseMapping.getExcludeColumns() != null && hbaseMapping.getExcludeColumns().contains(entry.getKey())) {
continue;
}
if (entry.getValue() != null) {
MappingConfig.ColumnItem columnItem = columnItems.get(entry.getKey());
byte[] bytes = typeConvert(columnItem, hbaseMapping, entry.getValue());
if (columnItem == null) {
String familyName = hbaseMapping.getFamily();
String qualifier = entry.getKey();
if (hbaseMapping.isUppercaseQualifier()) {
qualifier = qualifier.toUpperCase();
}
if (hbaseMapping.getRowKey() == null && i == 0) {
hRow.setRowKey(bytes);
} else {
hRow.addCell(familyName, qualifier, bytes);
}
} else {
if (columnItem.isRowKey()) {
if (columnItem.getRowKeyLen() != null && entry.getValue() != null) {
if (entry.getValue() instanceof Number) {
String v = String.format("%0" + columnItem.getRowKeyLen() + "d",
((Number) entry.getValue()).longValue());
bytes = Bytes.toBytes(v);
} else {
try {
String v = String.format("%0" + columnItem.getRowKeyLen() + "d",
Integer.parseInt((String) entry.getValue()));
bytes = Bytes.toBytes(v);
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
}
hRow.setRowKey(bytes);
} else {
hRow.addCell(columnItem.getFamily(), columnItem.getQualifier(), bytes);
}
}
}
i++;
}
}
/**
* 更新操作
*
* @param config 配置对象
* @param dml dml对象
*/
private void update(MappingConfig config, Dml dml) {
List<Map<String, Object>> data = dml.getData();
List<Map<String, Object>> old = dml.getOld();
if (old == null || old.isEmpty() || data == null || data.isEmpty()) {
return;
}
MappingConfig.HbaseMapping hbaseMapping = config.getHbaseMapping();
// if (!validHTable(config)) {
// logger.error("HBase table '{}' not exists",
// hbaseMapping.getHbaseTable());
// return;
// }
MappingConfig.ColumnItem rowKeyColumn = hbaseMapping.getRowKeyColumn();
int index = 0;
int i = 1;
boolean complete = false;
List<HRow> rows = new ArrayList<>();
out: for (Map<String, Object> r : data) {
byte[] rowKeyBytes;
if (hbaseMapping.getRowKey() != null) {
String[] rowKeyColumns = hbaseMapping.getRowKey().trim().split(",");
// 判断是否有复合主键修改
for (String updateColumn : old.get(index).keySet()) {
for (String rowKeyColumnName : rowKeyColumns) {
if (rowKeyColumnName.equalsIgnoreCase(updateColumn)) {
// 调用删除插入操作
deleteAndInsert(config, dml);
continue out;
}
}
}
String rowKeyVale = getRowKeys(rowKeyColumns, r);
rowKeyBytes = Bytes.toBytes(rowKeyVale);
} else if (rowKeyColumn == null) {
Map<String, Object> rowKey = data.get(0);
rowKeyBytes = typeConvert(null, hbaseMapping, rowKey.values().iterator().next());
} else {
rowKeyBytes = getRowKeyBytes(hbaseMapping, rowKeyColumn, r);
}
if (rowKeyBytes == null) throw new RuntimeException("rowKey值为空");
Map<String, MappingConfig.ColumnItem> columnItems = hbaseMapping.getColumnItems();
HRow hRow = new HRow(rowKeyBytes);
for (String updateColumn : old.get(index).keySet()) {
if (hbaseMapping.getExcludeColumns() != null
&& hbaseMapping.getExcludeColumns().contains(updateColumn)) {
continue;
}
MappingConfig.ColumnItem columnItem = columnItems.get(updateColumn);
if (columnItem == null) {
String family = hbaseMapping.getFamily();
String qualifier = updateColumn;
if (hbaseMapping.isUppercaseQualifier()) {
qualifier = qualifier.toUpperCase();
}
Object newVal = r.get(updateColumn);
if (newVal == null) {
hRow.addCell(family, qualifier, null);
} else {
hRow.addCell(family, qualifier, typeConvert(null, hbaseMapping, newVal));
}
} else {
// 排除修改id的情况
if (columnItem.isRowKey()) continue;
Object newVal = r.get(updateColumn);
if (newVal == null) {
hRow.addCell(columnItem.getFamily(), columnItem.getQualifier(), null);
} else {
hRow.addCell(columnItem.getFamily(),
columnItem.getQualifier(),
typeConvert(columnItem, hbaseMapping, newVal));
}
}
}
rows.add(hRow);
complete = false;
if (i % config.getHbaseMapping().getCommitBatch() == 0 && !rows.isEmpty()) {
hbaseTemplate.puts(hbaseMapping.getHbaseTable(), rows);
rows.clear();
complete = true;
}
i++;
index++;
}
if (!complete && !rows.isEmpty()) {
hbaseTemplate.puts(hbaseMapping.getHbaseTable(), rows);
}
}
private void delete(MappingConfig config, Dml dml) {
List<Map<String, Object>> data = dml.getData();
if (data == null || data.isEmpty()) {
return;
}
MappingConfig.HbaseMapping hbaseMapping = config.getHbaseMapping();
// if (!validHTable(config)) {
// logger.error("HBase table '{}' not exists",
// hbaseMapping.getHbaseTable());
// return;
// }
MappingConfig.ColumnItem rowKeyColumn = hbaseMapping.getRowKeyColumn();
boolean complete = false;
int i = 1;
Set<byte[]> rowKeys = new HashSet<>();
for (Map<String, Object> r : data) {
byte[] rowKeyBytes;
if (hbaseMapping.getRowKey() != null) {
String[] rowKeyColumns = hbaseMapping.getRowKey().trim().split(",");
String rowKeyVale = getRowKeys(rowKeyColumns, r);
rowKeyBytes = Bytes.toBytes(rowKeyVale);
} else if (rowKeyColumn == null) {
// 如果不需要类型转换
Map<String, Object> rowKey = data.get(0);
rowKeyBytes = typeConvert(null, hbaseMapping, rowKey.values().iterator().next());
} else {
rowKeyBytes = getRowKeyBytes(hbaseMapping, rowKeyColumn, r);
}
if (rowKeyBytes == null) throw new RuntimeException("rowKey值为空");
rowKeys.add(rowKeyBytes);
complete = false;
if (i % config.getHbaseMapping().getCommitBatch() == 0 && !rowKeys.isEmpty()) {
hbaseTemplate.deletes(hbaseMapping.getHbaseTable(), rowKeys);
rowKeys.clear();
complete = true;
}
i++;
}
if (!complete && !rowKeys.isEmpty()) {
hbaseTemplate.deletes(hbaseMapping.getHbaseTable(), rowKeys);
}
}
private void deleteAndInsert(MappingConfig config, Dml dml) {
List<Map<String, Object>> data = dml.getData();
List<Map<String, Object>> old = dml.getOld();
if (old == null || old.isEmpty() || data == null || data.isEmpty()) {
return;
}
MappingConfig.HbaseMapping hbaseMapping = config.getHbaseMapping();
String[] rowKeyColumns = hbaseMapping.getRowKey().trim().split(",");
int index = 0;
int i = 1;
boolean complete = false;
Set<byte[]> rowKeys = new HashSet<>();
List<HRow> rows = new ArrayList<>();
for (Map<String, Object> r : data) {
// 拼接老的rowKey
List<String> updateSubRowKey = new ArrayList<>();
for (String rowKeyColumnName : rowKeyColumns) {
for (String updateColumn : old.get(index).keySet()) {
if (rowKeyColumnName.equalsIgnoreCase(updateColumn)) {
updateSubRowKey.add(rowKeyColumnName);
}
}
}
if (updateSubRowKey.isEmpty()) {
throw new RuntimeException("没有更新复合主键的RowKey");
}
StringBuilder oldRowKey = new StringBuilder();
StringBuilder newRowKey = new StringBuilder();
for (String rowKeyColumnName : rowKeyColumns) {
newRowKey.append(r.get(rowKeyColumnName).toString()).append("|");
if (!updateSubRowKey.contains(rowKeyColumnName)) {
// 从data取
oldRowKey.append(r.get(rowKeyColumnName).toString()).append("|");
} else {
// 从old取
oldRowKey.append(old.get(index).get(rowKeyColumnName).toString()).append("|");
}
}
int len = newRowKey.length();
newRowKey.delete(len - 1, len);
len = oldRowKey.length();
oldRowKey.delete(len - 1, len);
byte[] newRowKeyBytes = Bytes.toBytes(newRowKey.toString());
byte[] oldRowKeyBytes = Bytes.toBytes(oldRowKey.toString());
rowKeys.add(oldRowKeyBytes);
HRow row = new HRow(newRowKeyBytes);
convertData2Row(hbaseMapping, row, r);
rows.add(row);
complete = false;
if (i % config.getHbaseMapping().getCommitBatch() == 0 && !rows.isEmpty()) {
hbaseTemplate.deletes(hbaseMapping.getHbaseTable(), rowKeys);
hbaseTemplate.puts(hbaseMapping.getHbaseTable(), rows);
rowKeys.clear();
rows.clear();
complete = true;
}
i++;
index++;
}
if (!complete && !rows.isEmpty()) {
hbaseTemplate.deletes(hbaseMapping.getHbaseTable(), rowKeys);
hbaseTemplate.puts(hbaseMapping.getHbaseTable(), rows);
}
}
/**
* 根据对应的类型进行转换
*
* @param columnItem 列项配置
* @param hbaseMapping hbase映射配置
* @param value 值
* @return 复合字段rowKey
*/
private static byte[] typeConvert(MappingConfig.ColumnItem columnItem, MappingConfig.HbaseMapping hbaseMapping,
Object value) {
if (value == null) {
return null;
}
byte[] bytes = null;
if (columnItem == null || columnItem.getType() == null || "".equals(columnItem.getType())) {
if (MappingConfig.Mode.STRING == hbaseMapping.getMode()) {
bytes = Bytes.toBytes(value.toString());
} else if (MappingConfig.Mode.NATIVE == hbaseMapping.getMode()) {
bytes = TypeUtil.toBytes(value);
} else if (MappingConfig.Mode.PHOENIX == hbaseMapping.getMode()) {
PhType phType = PhType.getType(value.getClass());
bytes = PhTypeUtil.toBytes(value, phType);
}
} else {
if (hbaseMapping.getMode() == MappingConfig.Mode.STRING) {
bytes = Bytes.toBytes(value.toString());
} else if (hbaseMapping.getMode() == MappingConfig.Mode.NATIVE) {
Type type = Type.getType(columnItem.getType());
bytes = TypeUtil.toBytes(value, type);
} else if (hbaseMapping.getMode() == MappingConfig.Mode.PHOENIX) {
PhType phType = PhType.getType(columnItem.getType());
bytes = PhTypeUtil.toBytes(value, phType);
}
}
return bytes;
}
/**
* 获取复合字段作为rowKey的拼接
*
* @param rowKeyColumns 复合rowK对应的字段
* @param data 数据
* @return
*/
private static String getRowKeys(String[] rowKeyColumns, Map<String, Object> data) {
StringBuilder rowKeyValue = new StringBuilder();
for (String rowKeyColumnName : rowKeyColumns) {
Object obj = data.get(rowKeyColumnName);
if (obj != null) {
rowKeyValue.append(obj.toString());
}
rowKeyValue.append("|");
}
int len = rowKeyValue.length();
if (len > 0) {
rowKeyValue.delete(len - 1, len);
}
return rowKeyValue.toString();
}
private static byte[] getRowKeyBytes(MappingConfig.HbaseMapping hbaseMapping, MappingConfig.ColumnItem rowKeyColumn,
Map<String, Object> rowData) {
Object val = rowData.get(rowKeyColumn.getColumn());
String v = null;
if (rowKeyColumn.getRowKeyLen() != null) {
if (val instanceof Number) {
v = String.format("%0" + rowKeyColumn.getRowKeyLen() + "d", (Number) ((Number) val).longValue());
} else if (val instanceof String) {
v = String.format("%0" + rowKeyColumn.getRowKeyLen() + "d", Long.parseLong((String) val));
}
}
if (v != null) {
return Bytes.toBytes(v);
} else {
return typeConvert(rowKeyColumn, hbaseMapping, val);
}
}
}
@@ -1,84 +0,0 @@
package com.alibaba.otter.canal.client.adapter.hbase.support;
import java.util.ArrayList;
import java.util.List;
/**
* HBase操作对象类
*
* @author machengyuan 2018-8-21 下午10:12:34
* @version 1.0.0
*/
public class HRow {
private byte[] rowKey;
private List<HCell> cells = new ArrayList<>();
public HRow(){
}
public HRow(byte[] rowKey){
this.rowKey = rowKey;
}
public byte[] getRowKey() {
return rowKey;
}
public void setRowKey(byte[] rowKey) {
this.rowKey = rowKey;
}
public List<HCell> getCells() {
return cells;
}
public void setCells(List<HCell> cells) {
this.cells = cells;
}
public void addCell(String family, String qualifier, byte[] value) {
HCell hCell = new HCell(family, qualifier, value);
cells.add(hCell);
}
public static class HCell {
private String family;
private String qualifier;
private byte[] value;
public HCell(){
}
public HCell(String family, String qualifier, byte[] value){
this.family = family;
this.qualifier = qualifier;
this.value = value;
}
public String getFamily() {
return family;
}
public void setFamily(String family) {
this.family = family;
}
public String getQualifier() {
return qualifier;
}
public void setQualifier(String qualifier) {
this.qualifier = qualifier;
}
public byte[] getValue() {
return value;
}
public void setValue(byte[] value) {
this.value = value;
}
}
}
@@ -1,191 +0,0 @@
package com.alibaba.otter.canal.client.adapter.hbase.support;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.client.Delete;
import org.apache.hadoop.hbase.client.HBaseAdmin;
import org.apache.hadoop.hbase.client.HTable;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.util.Bytes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* HBase操作模板
*
* @author machengyuan 2018-8-21 下午10:12:34
* @version 1.0.0
*/
public class HbaseTemplate {
private Logger logger = LoggerFactory.getLogger(this.getClass());
private Configuration hbaseConfig; // hbase配置对象
private Connection conn; // hbase连接
public HbaseTemplate(Configuration hbaseConfig){
this.hbaseConfig = hbaseConfig;
initConn();
}
private void initConn() {
try {
this.conn = ConnectionFactory.createConnection(hbaseConfig);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public Connection getConnection() {
if (conn == null || conn.isAborted() || conn.isClosed()) {
initConn();
}
return conn;
}
public boolean tableExists(String tableName) {
try (HBaseAdmin admin = (HBaseAdmin) getConnection().getAdmin()) {
return admin.tableExists(TableName.valueOf(tableName));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void createTable(String tableName, String... familyNames) {
try (HBaseAdmin admin = (HBaseAdmin) getConnection().getAdmin()) {
HTableDescriptor desc = new HTableDescriptor(TableName.valueOf(tableName));
// 添加列簇
if (familyNames != null) {
for (String familyName : familyNames) {
HColumnDescriptor hcd = new HColumnDescriptor(familyName);
desc.addFamily(hcd);
}
}
admin.createTable(desc);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void disableTable(String tableName) {
try (HBaseAdmin admin = (HBaseAdmin) getConnection().getAdmin()) {
admin.disableTable(tableName);
} catch (IOException e) {
logger.error(e.getMessage(), e);
throw new RuntimeException(e);
}
}
public void deleteTable(String tableName) {
try (HBaseAdmin admin = (HBaseAdmin) getConnection().getAdmin()) {
if (admin.isTableEnabled(tableName)) {
disableTable(tableName);
}
admin.deleteTable(tableName);
} catch (IOException e) {
logger.error(e.getMessage(), e);
throw new RuntimeException(e);
}
}
/**
* 插入一行数据
*
* @param tableName 表名
* @param hRow 行数据对象
* @return 是否成功
*/
public Boolean put(String tableName, HRow hRow) {
boolean flag = false;
try {
HTable table = (HTable) getConnection().getTable(TableName.valueOf(tableName));
Put put = new Put(hRow.getRowKey());
for (HRow.HCell hCell : hRow.getCells()) {
put.addColumn(Bytes.toBytes(hCell.getFamily()), Bytes.toBytes(hCell.getQualifier()), hCell.getValue());
}
table.put(put);
flag = true;
} catch (Exception e) {
logger.error(e.getMessage(), e);
throw new RuntimeException(e);
}
return flag;
}
/**
* 批量插入
*
* @param tableName 表名
* @param rows 行数据对象集合
* @return 是否成功
*/
public Boolean puts(String tableName, List<HRow> rows) {
boolean flag = false;
try {
HTable table = (HTable) getConnection().getTable(TableName.valueOf(tableName));
List<Put> puts = new ArrayList<>();
for (HRow hRow : rows) {
Put put = new Put(hRow.getRowKey());
for (HRow.HCell hCell : hRow.getCells()) {
put.addColumn(Bytes.toBytes(hCell.getFamily()),
Bytes.toBytes(hCell.getQualifier()),
hCell.getValue());
}
puts.add(put);
}
if (!puts.isEmpty()) {
table.put(puts);
}
flag = true;
} catch (Exception e) {
logger.error(e.getMessage(), e);
throw new RuntimeException(e);
}
return flag;
}
/**
* 批量删除数据
*
* @param tableName 表名
* @param rowKeys rowKey集合
* @return 是否成功
*/
public Boolean deletes(String tableName, Set<byte[]> rowKeys) {
boolean flag = false;
try {
HTable table = (HTable) getConnection().getTable(TableName.valueOf(tableName));
List<Delete> deletes = new ArrayList<>();
for (byte[] rowKey : rowKeys) {
Delete delete = new Delete(rowKey);
deletes.add(delete);
}
if (!deletes.isEmpty()) {
table.delete(deletes);
}
flag = true;
} catch (Exception e) {
logger.error(e.getMessage(), e);
throw new RuntimeException(e);
}
return flag;
}
public void close() throws IOException {
if (conn != null) {
conn.close();
}
}
}
@@ -1,73 +0,0 @@
package com.alibaba.otter.canal.client.adapter.hbase.support;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Date;
/**
* Phoenix类型
*
* @author machengyuan 2018-8-21 下午06:12:34
* @version 1.0.0
*/
public enum PhType {
DEFAULT, UNSIGNED_INT, UNSIGNED_LONG, UNSIGNED_TINYINT, UNSIGNED_SMALLINT, UNSIGNED_FLOAT,
UNSIGNED_DOUBLE, INTEGER, BIGINT, TINYINT, SMALLINT, FLOAT, DOUBLE, DECIMAL, BOOLEAN, UNSIGNED_TIME,
UNSIGNED_DATE, UNSIGNED_TIMESTAMP, TIME, DATE, TIMESTAMP, VARCHAR, VARBINARY;
private static Logger logger = LoggerFactory.getLogger(PhType.class);
public static PhType getType(Class<?> javaType) {
if (javaType == null) return DEFAULT;
PhType phType;
if (Integer.class == javaType || int.class == javaType) {
phType = INTEGER;
} else if (Long.class == javaType || long.class == javaType) {
phType = BIGINT;
} else if (Byte.class == javaType || byte.class == javaType) {
phType = TINYINT;
} else if (Short.class == javaType || short.class == javaType) {
phType = SMALLINT;
} else if (Float.class == javaType || float.class == javaType) {
phType = FLOAT;
} else if (Double.class == javaType || double.class == javaType) {
phType = DOUBLE;
} else if (Boolean.class == javaType || boolean.class == javaType) {
phType = BOOLEAN;
} else if (java.sql.Date.class == javaType) {
phType = DATE;
} else if (Time.class == javaType) {
phType = DATE;
} else if (Timestamp.class == javaType) {
phType = TIMESTAMP;
} else if (Date.class == javaType) {
phType = DATE;
} else if (byte[].class == javaType) {
phType = VARBINARY;
} else if (String.class == javaType) {
phType = VARCHAR;
} else if (BigDecimal.class == javaType) {
phType = DECIMAL;
} else if (BigInteger.class == javaType) {
phType = UNSIGNED_LONG;
} else {
phType = DEFAULT;
}
return phType;
}
public static PhType getType(String type) {
if (type == null) return DEFAULT;
try {
return PhType.valueOf(type.toUpperCase());
} catch (Exception e) {
logger.error(e.getMessage(), e);
return DEFAULT;
}
}
}
@@ -1,609 +0,0 @@
package com.alibaba.otter.canal.client.adapter.hbase.support;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.math.RoundingMode;
import java.sql.Timestamp;
import java.util.Date;
import org.apache.hadoop.hbase.util.Bytes;
import com.alibaba.otter.canal.client.adapter.support.Util;
import com.google.common.math.LongMath;
/**
* Phoenix类型转换工具类
*
* @author machengyuan 2018-8-21 下午06:14:26
* @version 1.0.0
*/
public class PhTypeUtil {
public static byte[] toBytes(Object v, PhType phType) {
if (v == null) return null;
byte[] b = null;
if (phType == PhType.DEFAULT) {
PhType phType1 = PhType.getType(v.getClass());
if (phType1 != null && phType1 != PhType.DEFAULT) {
toBytes(v, phType1);
}
} else if (phType == PhType.INTEGER) {
b = new byte[Bytes.SIZEOF_INT];
encodeInt(((Number) v).intValue(), b, 0);
} else if (phType == PhType.UNSIGNED_INT) {
b = new byte[Bytes.SIZEOF_INT];
encodeUnsignedInt(((Number) v).intValue(), b, 0);
} else if (phType == PhType.BIGINT) {
b = new byte[Bytes.SIZEOF_LONG];
encodeLong(((Number) v).longValue(), b, 0);
} else if (phType == PhType.UNSIGNED_LONG) {
b = new byte[Bytes.SIZEOF_LONG];
encodeUnsignedLong(((Number) v).longValue(), b, 0);
} else if (phType == PhType.SMALLINT) {
b = new byte[Bytes.SIZEOF_SHORT];
encodeShort(((Number) v).shortValue(), b, 0);
} else if (phType == PhType.UNSIGNED_SMALLINT) {
b = new byte[Bytes.SIZEOF_SHORT];
encodeUnsignedShort(((Number) v).shortValue(), b, 0);
} else if (phType == PhType.TINYINT) {
b = new byte[Bytes.SIZEOF_BYTE];
encodeByte(((Number) v).byteValue(), b, 0);
} else if (phType == PhType.UNSIGNED_TINYINT) {
b = new byte[Bytes.SIZEOF_BYTE];
encodeUnsignedByte(((Number) v).byteValue(), b, 0);
} else if (phType == PhType.FLOAT) {
b = new byte[Bytes.SIZEOF_FLOAT];
encodeFloat(((Number) v).floatValue(), b, 0);
} else if (phType == PhType.UNSIGNED_FLOAT) {
b = new byte[Bytes.SIZEOF_FLOAT];
encodeUnsignedFloat(((Number) v).floatValue(), b, 0);
} else if (phType == PhType.DOUBLE) {
b = new byte[Bytes.SIZEOF_DOUBLE];
encodeDouble(((Number) v).doubleValue(), b, 0);
} else if (phType == PhType.UNSIGNED_DOUBLE) {
b = new byte[Bytes.SIZEOF_DOUBLE];
encodeUnsignedDouble(((Number) v).doubleValue(), b, 0);
} else if (phType == PhType.BOOLEAN) {
if ((Boolean) v) {
b = new byte[] { 1 };
} else {
b = new byte[] { 0 };
}
} else if (phType == PhType.TIME || phType == PhType.DATE) {
b = new byte[Bytes.SIZEOF_LONG];
encodeDate(v, b, 0);
} else if (phType == PhType.TIMESTAMP) {
b = new byte[Bytes.SIZEOF_LONG + Bytes.SIZEOF_INT];
encodeTimestamp(v, b, 0);
} else if (phType == PhType.UNSIGNED_TIME || phType == PhType.UNSIGNED_DATE) {
b = new byte[Bytes.SIZEOF_LONG];
encodeUnsignedDate(v, b, 0);
} else if (phType == PhType.UNSIGNED_TIMESTAMP) {
b = new byte[Bytes.SIZEOF_LONG + Bytes.SIZEOF_INT];
encodeUnsignedTimestamp(v, b, 0);
} else if (phType == PhType.VARBINARY) {
b = (byte[]) v;
} else if (phType == PhType.VARCHAR) {
b = Bytes.toBytes(v.toString());
} else if (phType == PhType.DECIMAL) {
if (v instanceof BigDecimal) {
b = encodeDecimal(v);
} else if (v instanceof Number) {
b = encodeDecimal(new BigDecimal(v.toString()));
}
}
return b;
}
public static Object toObject(byte[] b, PhType phType) {
if (b == null) return null;
Object v = null;
if (phType == PhType.INTEGER) {
v = decodeInt(b, 0);
} else if (phType == PhType.UNSIGNED_INT) {
v = decodeUnsignedInt(b, 0);
} else if (phType == PhType.BIGINT) {
v = decodeLong(b, 0);
} else if (phType == PhType.UNSIGNED_LONG) {
v = decodeUnsignedLong(b, 0);
} else if (phType == PhType.SMALLINT) {
v = decodeShort(b, 0);
} else if (phType == PhType.UNSIGNED_SMALLINT) {
v = decodeUnsignedShort(b, 0);
} else if (phType == PhType.TINYINT) {
v = decodeByte(b, 0);
} else if (phType == PhType.UNSIGNED_TINYINT) {
v = decodeUnsignedByte(b, 0);
} else if (phType == PhType.FLOAT) {
v = decodeFloat(b, 0);
} else if (phType == PhType.UNSIGNED_FLOAT) {
v = decodeUnsignedFloat(b, 0);
} else if (phType == PhType.DOUBLE) {
v = decodeDouble(b, 0);
} else if (phType == PhType.UNSIGNED_DOUBLE) {
v = decodeUnsignedDouble(b, 0);
} else if (phType == PhType.BOOLEAN) {
checkForSufficientLength(b, 0, Bytes.SIZEOF_BOOLEAN);
if (b[0] == 1) {
v = true;
} else if (b[0] == 0) {
v = false;
}
} else if (phType == PhType.DATE) {
v = new java.sql.Date(decodeLong(b, 0));
} else if (phType == PhType.TIME) {
v = new java.sql.Time(decodeLong(b, 0));
} else if (phType == PhType.TIMESTAMP) {
long millisDeserialized = decodeLong(b, 0);
Timestamp ts = new Timestamp(millisDeserialized);
int nanosDeserialized = decodeUnsignedInt(b, Bytes.SIZEOF_LONG);
ts.setNanos(nanosDeserialized < 1000000 ? ts.getNanos() + nanosDeserialized : nanosDeserialized);
v = ts;
} else if (phType == PhType.UNSIGNED_TIME || phType == PhType.UNSIGNED_DATE) {
v = new Date(decodeUnsignedLong(b, 0));
} else if (phType == PhType.UNSIGNED_TIMESTAMP) {
long millisDeserialized = decodeUnsignedLong(b, 0);
Timestamp ts = new Timestamp(millisDeserialized);
int nanosDeserialized = decodeUnsignedInt(b, Bytes.SIZEOF_LONG);
ts.setNanos(nanosDeserialized < 1000000 ? ts.getNanos() + nanosDeserialized : nanosDeserialized);
v = ts;
} else if (phType == PhType.VARBINARY) {
v = b;
} else if (phType == PhType.VARCHAR || phType == PhType.DEFAULT) {
v = Bytes.toString(b);
} else if (phType == PhType.DECIMAL) {
v = decodeDecimal(b, 0, b.length);
}
return v;
}
private static int decodeInt(byte[] bytes, int o) {
checkForSufficientLength(bytes, o, Bytes.SIZEOF_INT);
int v;
v = bytes[o] ^ 0x80; // Flip sign bit back
for (int i = 1; i < Bytes.SIZEOF_INT; i++) {
v = (v << 8) + (bytes[o + i] & 0xff);
}
return v;
}
private static int encodeInt(int v, byte[] b, int o) {
checkForSufficientLength(b, o, Bytes.SIZEOF_INT);
b[o + 0] = (byte) ((v >> 24) ^ 0x80); // Flip sign bit so that INTEGER
// is binary comparable
b[o + 1] = (byte) (v >> 16);
b[o + 2] = (byte) (v >> 8);
b[o + 3] = (byte) v;
return Bytes.SIZEOF_INT;
}
private static int decodeUnsignedInt(byte[] b, int o) {
checkForSufficientLength(b, o, Bytes.SIZEOF_INT);
int v = Bytes.toInt(b, o);
if (v < 0) {
throw new RuntimeException();
}
return v;
}
private static int encodeUnsignedInt(int v, byte[] b, int o) {
checkForSufficientLength(b, o, Bytes.SIZEOF_INT);
if (v < 0) {
throw new RuntimeException();
}
Bytes.putInt(b, o, v);
return Bytes.SIZEOF_INT;
}
private static long decodeLong(byte[] bytes, int o) {
checkForSufficientLength(bytes, o, Bytes.SIZEOF_LONG);
long v;
byte b = bytes[o];
v = b ^ 0x80; // Flip sign bit back
for (int i = 1; i < Bytes.SIZEOF_LONG; i++) {
b = bytes[o + i];
v = (v << 8) + (b & 0xff);
}
return v;
}
private static int encodeLong(long v, byte[] b, int o) {
checkForSufficientLength(b, o, Bytes.SIZEOF_LONG);
b[o + 0] = (byte) ((v >> 56) ^ 0x80); // Flip sign bit so that INTEGER
// is binary comparable
b[o + 1] = (byte) (v >> 48);
b[o + 2] = (byte) (v >> 40);
b[o + 3] = (byte) (v >> 32);
b[o + 4] = (byte) (v >> 24);
b[o + 5] = (byte) (v >> 16);
b[o + 6] = (byte) (v >> 8);
b[o + 7] = (byte) v;
return Bytes.SIZEOF_LONG;
}
private static long decodeUnsignedLong(byte[] b, int o) {
checkForSufficientLength(b, o, Bytes.SIZEOF_LONG);
long v = 0;
for (int i = o; i < o + Bytes.SIZEOF_LONG; i++) {
v <<= 8;
v ^= b[i] & 0xFF;
}
if (v < 0) {
throw new RuntimeException();
}
return v;
}
private static int encodeUnsignedLong(long v, byte[] b, int o) {
checkForSufficientLength(b, o, Bytes.SIZEOF_LONG);
if (v < 0) {
throw new RuntimeException();
}
Bytes.putLong(b, o, v);
return Bytes.SIZEOF_LONG;
}
private static short decodeShort(byte[] b, int o) {
checkForSufficientLength(b, o, Bytes.SIZEOF_SHORT);
int v;
v = b[o] ^ 0x80; // Flip sign bit back
for (int i = 1; i < Bytes.SIZEOF_SHORT; i++) {
v = (v << 8) + (b[o + i] & 0xff);
}
return (short) v;
}
private static int encodeShort(short v, byte[] b, int o) {
checkForSufficientLength(b, o, Bytes.SIZEOF_SHORT);
b[o + 0] = (byte) ((v >> 8) ^ 0x80); // Flip sign bit so that Short is
// binary comparable
b[o + 1] = (byte) v;
return Bytes.SIZEOF_SHORT;
}
private static short decodeUnsignedShort(byte[] b, int o) {
checkForSufficientLength(b, o, Bytes.SIZEOF_SHORT);
short v = Bytes.toShort(b, o);
if (v < 0) {
throw new RuntimeException();
}
return v;
}
private static int encodeUnsignedShort(short v, byte[] b, int o) {
checkForSufficientLength(b, o, Bytes.SIZEOF_SHORT);
if (v < 0) {
throw new RuntimeException();
}
Bytes.putShort(b, o, v);
return Bytes.SIZEOF_SHORT;
}
private static byte decodeByte(byte[] b, int o) {
checkForSufficientLength(b, o, Bytes.SIZEOF_BYTE);
int v;
v = b[o] ^ 0x80; // Flip sign bit back
return (byte) v;
}
private static int encodeByte(byte v, byte[] b, int o) {
checkForSufficientLength(b, o, Bytes.SIZEOF_BYTE);
b[o] = (byte) (v ^ 0x80); // Flip sign bit so that Short is binary
// comparable
return Bytes.SIZEOF_BYTE;
}
private static byte decodeUnsignedByte(byte[] b, int o) {
checkForSufficientLength(b, o, Bytes.SIZEOF_BYTE);
byte v = b[o];
if (v < 0) {
throw new RuntimeException();
}
return v;
}
private static int encodeUnsignedByte(byte v, byte[] b, int o) {
if (v < 0) {
throw new RuntimeException();
}
Bytes.putByte(b, o, v);
return Bytes.SIZEOF_BYTE;
}
private static float decodeFloat(byte[] b, int o) {
checkForSufficientLength(b, o, Bytes.SIZEOF_INT);
int value;
value = Bytes.toInt(b, o);
value--;
value ^= (~value >> Integer.SIZE - 1) | Integer.MIN_VALUE;
return Float.intBitsToFloat(value);
}
private static int encodeFloat(float v, byte[] b, int o) {
checkForSufficientLength(b, o, Bytes.SIZEOF_FLOAT);
int i = Float.floatToIntBits(v);
i = (i ^ ((i >> Integer.SIZE - 1) | Integer.MIN_VALUE)) + 1;
Bytes.putInt(b, o, i);
return Bytes.SIZEOF_FLOAT;
}
private static float decodeUnsignedFloat(byte[] b, int o) {
checkForSufficientLength(b, o, Bytes.SIZEOF_FLOAT);
float v = Bytes.toFloat(b, o);
if (v < 0) {
throw new RuntimeException();
}
return v;
}
private static int encodeUnsignedFloat(float v, byte[] b, int o) {
checkForSufficientLength(b, o, Bytes.SIZEOF_FLOAT);
if (v < 0) {
throw new RuntimeException();
}
Bytes.putFloat(b, o, v);
return Bytes.SIZEOF_FLOAT;
}
private static double decodeDouble(byte[] bytes, int o) {
checkForSufficientLength(bytes, o, Bytes.SIZEOF_LONG);
long l;
l = Bytes.toLong(bytes, o);
l--;
l ^= (~l >> Long.SIZE - 1) | Long.MIN_VALUE;
return Double.longBitsToDouble(l);
}
private static int encodeDouble(double v, byte[] b, int o) {
checkForSufficientLength(b, o, Bytes.SIZEOF_LONG);
long l = Double.doubleToLongBits(v);
l = (l ^ ((l >> Long.SIZE - 1) | Long.MIN_VALUE)) + 1;
Bytes.putLong(b, o, l);
return Bytes.SIZEOF_LONG;
}
private static double decodeUnsignedDouble(byte[] b, int o) {
checkForSufficientLength(b, o, Bytes.SIZEOF_DOUBLE);
double v = Bytes.toDouble(b, o);
if (v < 0) {
throw new RuntimeException();
}
return v;
}
private static int encodeUnsignedDouble(double v, byte[] b, int o) {
checkForSufficientLength(b, o, Bytes.SIZEOF_DOUBLE);
if (v < 0) {
throw new RuntimeException();
}
Bytes.putDouble(b, o, v);
return Bytes.SIZEOF_DOUBLE;
}
private static int encodeDate(Object v, byte[] b, int o) {
if (v instanceof Date) {
encodeLong(((Date) v).getTime(), b, 0);
} else if (v instanceof String) {
String dateStr = (String) v;
Date date;
try {
date = Util.parseDate(dateStr);
if (date != null) {
encodeLong(date.getTime(), b, 0);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return Bytes.SIZEOF_LONG;
}
private static int encodeTimestamp(Object v, byte[] b, int o) {
if (v instanceof Timestamp) {
Timestamp ts = (Timestamp) v;
encodeLong(ts.getTime(), b, o);
Bytes.putInt(b, Bytes.SIZEOF_LONG, ts.getNanos() % 1000000);
} else {
encodeDate(v, b, o);
}
return Bytes.SIZEOF_LONG + Bytes.SIZEOF_INT;
}
private static int encodeUnsignedDate(Object v, byte[] b, int o) {
if (v instanceof Date) {
encodeUnsignedLong(((Date) v).getTime(), b, 0);
} else if (v instanceof String) {
String dateStr = (String) v;
Date date;
try {
date = Util.parseDate(dateStr);
if (date != null) {
encodeUnsignedLong(date.getTime(), b, 0);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return Bytes.SIZEOF_LONG;
}
private static int encodeUnsignedTimestamp(Object v, byte[] b, int o) {
if (v instanceof Timestamp) {
Timestamp ts = (Timestamp) v;
encodeUnsignedLong(ts.getTime(), b, o);
Bytes.putInt(b, Bytes.SIZEOF_LONG, ts.getNanos() % 1000000);
} else {
encodeUnsignedDate(v, b, o);
}
return Bytes.SIZEOF_LONG + Bytes.SIZEOF_INT;
}
private static byte[] encodeDecimal(Object object) {
if (object == null) {
return new byte[0];
}
BigDecimal v = (BigDecimal) object;
v = v.round(DEFAULT_MATH_CONTEXT).stripTrailingZeros();
int len = getLength(v);
byte[] result = new byte[Math.min(len, 21)];
decimalToBytes(v, result, 0, len);
return result;
}
private static BigDecimal decodeDecimal(byte[] bytes, int offset, int length) {
if (length == 1 && bytes[offset] == ZERO_BYTE) {
return BigDecimal.ZERO;
}
int signum = ((bytes[offset] & 0x80) == 0) ? -1 : 1;
int scale;
int index;
int digitOffset;
long multiplier = 100L;
int begIndex = offset + 1;
if (signum == 1) {
scale = (byte) (((bytes[offset] & 0x7F) - 65) * -2);
index = offset + length;
digitOffset = POS_DIGIT_OFFSET;
} else {
scale = (byte) ((~bytes[offset] - 65 - 128) * -2);
index = offset + length - (bytes[offset + length - 1] == NEG_TERMINAL_BYTE ? 1 : 0);
digitOffset = -NEG_DIGIT_OFFSET;
}
length = index - offset;
long l = signum * bytes[--index] - digitOffset;
if (l % 10 == 0) { // trailing zero
scale--; // drop trailing zero and compensate in the scale
l /= 10;
multiplier = 10;
}
// Use long arithmetic for as long as we can
while (index > begIndex) {
if (l >= MAX_LONG_FOR_DESERIALIZE || multiplier >= Long.MAX_VALUE / 100) {
multiplier = LongMath.divide(multiplier, 100L, RoundingMode.UNNECESSARY);
break; // Exit loop early so we don't overflow our multiplier
}
int digit100 = signum * bytes[--index] - digitOffset;
l += digit100 * multiplier;
multiplier = LongMath.checkedMultiply(multiplier, 100);
}
BigInteger bi;
// If still more digits, switch to BigInteger arithmetic
if (index > begIndex) {
bi = BigInteger.valueOf(l);
BigInteger biMultiplier = BigInteger.valueOf(multiplier).multiply(ONE_HUNDRED);
do {
int digit100 = signum * bytes[--index] - digitOffset;
bi = bi.add(biMultiplier.multiply(BigInteger.valueOf(digit100)));
biMultiplier = biMultiplier.multiply(ONE_HUNDRED);
} while (index > begIndex);
if (signum == -1) {
bi = bi.negate();
}
} else {
bi = BigInteger.valueOf(l * signum);
}
// Update the scale based on the precision
scale += (length - 2) * 2;
BigDecimal v = new BigDecimal(bi, scale);
return v;
}
private static int getLength(BigDecimal v) {
int signum = v.signum();
if (signum == 0) { // Special case for zero
return 1;
}
return (signum < 0 ? 2 : 1) + (v.precision() + 1 + (v.scale() % 2 == 0 ? 0 : 1)) / 2;
}
private static final int MAX_PRECISION = 38;
private static final MathContext DEFAULT_MATH_CONTEXT = new MathContext(MAX_PRECISION, RoundingMode.HALF_UP);
private static final Integer MAX_BIG_DECIMAL_BYTES = 21;
private static final byte ZERO_BYTE = (byte) 0x80;
private static final byte NEG_TERMINAL_BYTE = (byte) 102;
private static final int EXP_BYTE_OFFSET = 65;
private static final int POS_DIGIT_OFFSET = 1;
private static final int NEG_DIGIT_OFFSET = 101;
private static final BigInteger MAX_LONG = BigInteger.valueOf(Long.MAX_VALUE);
private static final BigInteger MIN_LONG = BigInteger.valueOf(Long.MIN_VALUE);
private static final BigInteger ONE_HUNDRED = BigInteger.valueOf(100);
private static final long MAX_LONG_FOR_DESERIALIZE = Long.MAX_VALUE / 1000;
private static int decimalToBytes(BigDecimal v, byte[] result, final int offset, int length) {
int signum = v.signum();
if (signum == 0) {
result[offset] = ZERO_BYTE;
return 1;
}
int index = offset + length;
int scale = v.scale();
int expOffset = scale % 2 * (scale < 0 ? -1 : 1);
int multiplyBy;
BigInteger divideBy;
if (expOffset == 0) {
multiplyBy = 1;
divideBy = ONE_HUNDRED;
} else {
multiplyBy = 10;
divideBy = BigInteger.TEN;
}
// Normalize the scale based on what is necessary to end up with a base
// 100
// decimal (i.e. 10.123e3)
int digitOffset;
BigInteger compareAgainst;
if (signum == 1) {
digitOffset = POS_DIGIT_OFFSET;
compareAgainst = MAX_LONG;
scale -= (length - 2) * 2;
result[offset] = (byte) ((-(scale + expOffset) / 2 + EXP_BYTE_OFFSET) | 0x80);
} else {
digitOffset = NEG_DIGIT_OFFSET;
compareAgainst = MIN_LONG;
// Scale adjustment shouldn't include terminal byte in length
scale -= (length - 2 - 1) * 2;
result[offset] = (byte) (~(-(scale + expOffset) / 2 + EXP_BYTE_OFFSET + 128) & 0x7F);
if (length <= MAX_BIG_DECIMAL_BYTES) {
result[--index] = NEG_TERMINAL_BYTE;
} else {
// Adjust length and offset down because we don't have enough
// room
length = MAX_BIG_DECIMAL_BYTES;
index = offset + length;
}
}
BigInteger bi = v.unscaledValue();
// Use BigDecimal arithmetic until we can fit into a long
while (bi.compareTo(compareAgainst) * signum > 0) {
BigInteger[] dandr = bi.divideAndRemainder(divideBy);
bi = dandr[0];
int digit = dandr[1].intValue();
result[--index] = (byte) (digit * multiplyBy + digitOffset);
multiplyBy = 1;
divideBy = ONE_HUNDRED;
}
long l = bi.longValue();
do {
long divBy = 100 / multiplyBy;
long digit = l % divBy;
l /= divBy;
result[--index] = (byte) (digit * multiplyBy + digitOffset);
multiplyBy = 1;
} while (l != 0);
return length;
}
private static void checkForSufficientLength(byte[] b, int offset, int requiredLength) {
if (b.length < offset + requiredLength) {
throw new RuntimeException(
"Expected length of at least " + requiredLength + " bytes, but had " + (b.length - offset));
}
}
}
@@ -1,67 +0,0 @@
package com.alibaba.otter.canal.client.adapter.hbase.support;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Date;
/**
* Java类型
*
* @author machengyuan 2018-8-21 下午06:11:36
* @version 1.0.0
*/
public enum Type {
DEFAULT, STRING, INTEGER, LONG, SHORT, BOOLEAN, FLOAT, DOUBLE, BIGDECIMAL, DATE, BYTE, BYTES;
private static Logger logger = LoggerFactory.getLogger(Type.class);
public static Type getType(String type) {
if (type == null) {
return DEFAULT;
}
try {
return Type.valueOf(type);
} catch (Exception e) {
logger.error(e.getMessage(), e);
return DEFAULT;
}
}
public static Type getType(Class<?> javaType) {
if (javaType == null) {
return DEFAULT;
}
Type type;
if (Integer.class == javaType || int.class == javaType) {
type = INTEGER;
} else if (Long.class == javaType || long.class == javaType) {
type = LONG;
} else if (Byte.class == javaType || byte.class == javaType) {
type = BYTE;
} else if (Short.class == javaType || short.class == javaType) {
type = SHORT;
} else if (Float.class == javaType || float.class == javaType) {
type = FLOAT;
} else if (Double.class == javaType || double.class == javaType) {
type = DOUBLE;
} else if (Boolean.class == javaType || boolean.class == javaType) {
type = BOOLEAN;
} else if (Date.class == javaType) {
type = DATE;
} else if (byte[].class == javaType) {
type = BYTES;
} else if (String.class == javaType) {
type = STRING;
} else if (BigDecimal.class == javaType) {
type = BIGDECIMAL;
} else if (BigInteger.class == javaType) {
type = LONG;
} else {
type = DEFAULT;
}
return type;
}
}
@@ -1,189 +0,0 @@
package com.alibaba.otter.canal.client.adapter.hbase.support;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Date;
import org.apache.hadoop.hbase.util.Bytes;
/**
* Java类型转换工具类
*
* @author machengyuan 2018-8-21 下午06:12:34
* @version 1.0.0
*/
public class TypeUtil {
public static byte[] toBytes(Object obj) {
if (obj == null) {
return null;
}
byte[] bytes;
Class<?> clazz = obj.getClass();
if (clazz == String.class) {
bytes = Bytes.toBytes((String) obj);
} else if (clazz == Integer.class || clazz == int.class) {
bytes = Bytes.toBytes((Integer) obj);
} else if (clazz == Long.class || clazz == long.class) {
bytes = Bytes.toBytes((Long) obj);
} else if (clazz == Short.class || clazz == short.class) {
bytes = Bytes.toBytes((Short) obj);
} else if (clazz == Boolean.class || clazz == boolean.class) {
bytes = Bytes.toBytes((Boolean) obj);
} else if (clazz == Float.class || clazz == float.class) {
bytes = Bytes.toBytes((Float) obj);
} else if (clazz == Double.class || clazz == double.class) {
bytes = Bytes.toBytes((Double) obj);
} else if (clazz == Byte.class || clazz == byte.class) {
bytes = new byte[] { (byte) obj };
} else if (clazz == BigDecimal.class) {
bytes = Bytes.toBytes((BigDecimal) obj);
} else if (clazz == BigInteger.class) {
bytes = Bytes.toBytes(((BigInteger) obj).longValue());
} else if (clazz == Date.class) {
bytes = Bytes.toBytes(((Date) obj).getTime());
} else if (clazz == byte[].class) {
bytes = (byte[]) obj;
} else {
// 其余类型统一转换为string
bytes = Bytes.toBytes(obj.toString());
}
return bytes;
}
public static byte[] toBytes(Object v, Type type) {
if (v == null) {
return null;
}
byte[] b = null;
if (type == Type.DEFAULT) {
Type type1 = Type.getType(v.getClass());
if (type1 != null && type1 != Type.DEFAULT) {
b = toBytes(v, type1);
}
} else if (type == Type.STRING) {
b = Bytes.toBytes(v.toString());
} else if (type == Type.INTEGER) {
b = Bytes.toBytes(((Number) v).intValue());
} else if (type == Type.LONG) {
b = Bytes.toBytes(((Number) v).longValue());
} else if (type == Type.SHORT) {
b = Bytes.toBytes(((Number) v).shortValue());
} else if (type == Type.BYTE) {
b = Bytes.toBytes(((Number) v).byteValue());
} else if (type == Type.FLOAT) {
b = Bytes.toBytes(((Number) v).floatValue());
} else if (type == Type.DOUBLE) {
b = Bytes.toBytes(((Number) v).doubleValue());
} else if (type == Type.BOOLEAN) {
b = Bytes.toBytes(((Boolean) v));
} else if (type == Type.DATE) {
b = Bytes.toBytes(((Date) v).getTime());
} else if (type == Type.BYTES) {
b = (byte[]) v;
} else if (type == Type.BIGDECIMAL) {
if (v instanceof BigDecimal) {
b = Bytes.toBytes((BigDecimal) v);
} else {
b = Bytes.toBytes(new BigDecimal(v.toString()));
}
}
return b;
}
@SuppressWarnings("unchecked")
public static <T> T toObject(byte[] bytes, Class<T> clazz) {
if (bytes == null) {
return null;
}
Object res;
if (String.class == clazz) {
res = Bytes.toString(bytes);
} else if (Integer.class == clazz || int.class == clazz) {
res = Bytes.toInt(bytes);
} else if (Long.class == clazz || long.class == clazz) {
res = Bytes.toLong(bytes);
} else if (Short.class == clazz || short.class == clazz) {
res = Bytes.toShort(bytes);
} else if (Boolean.class == clazz || boolean.class == clazz) {
res = Bytes.toBoolean(bytes);
} else if (Float.class == clazz || float.class == clazz) {
res = Bytes.toFloat(bytes);
} else if (Double.class == clazz || double.class == clazz) {
res = Bytes.toDouble(bytes);
} else if (Byte.class == clazz || byte.class == clazz) {
res = bytes[0];
} else if (BigDecimal.class == clazz) {
res = Bytes.toBigDecimal(bytes);
} else if (BigInteger.class == clazz) {
res = Bytes.toLong(bytes);
} else if (java.sql.Date.class == clazz) {
long ts = Bytes.toLong(bytes);
res = new java.sql.Date(ts);
} else if (Time.class == clazz) {
long ts = Bytes.toLong(bytes);
res = new Time(ts);
} else if (Timestamp.class == clazz) {
long ts = Bytes.toLong(bytes);
res = new Timestamp(ts);
} else if (Date.class == clazz) {
long ts = Bytes.toLong(bytes);
res = new Date(ts);
} else {
throw new IllegalArgumentException("mismatch class type");
}
return (T) res;
}
@SuppressWarnings("unchecked")
public static <T> T toObject(byte[] bytes, Type type) {
if (bytes == null) {
return null;
}
Object res = null;
if (type == Type.STRING || type == Type.DEFAULT) {
res = Bytes.toString(bytes);
} else if (type == Type.INTEGER) {
if (bytes.length == Bytes.SIZEOF_INT) {
res = Bytes.toInt(bytes);
}
} else if (type == Type.LONG) {
if (bytes.length == Bytes.SIZEOF_LONG) {
res = Bytes.toLong(bytes);
}
} else if (type == Type.SHORT) {
if (bytes.length == Bytes.SIZEOF_SHORT) {
res = Bytes.toShort(bytes);
}
} else if (type == Type.BYTE) {
if (bytes.length == Bytes.SIZEOF_BYTE) {
res = bytes[0];
}
} else if (type == Type.FLOAT) {
if (bytes.length == Bytes.SIZEOF_FLOAT) {
res = Bytes.toFloat(bytes);
}
} else if (type == Type.DOUBLE) {
if (bytes.length == Bytes.SIZEOF_DOUBLE) {
res = Bytes.toDouble(bytes);
}
} else if (type == Type.BOOLEAN) {
if (bytes.length == Bytes.SIZEOF_BOOLEAN) {
res = Bytes.toBoolean(bytes);
}
} else if (type == Type.DATE) {
if (bytes.length == Bytes.SIZEOF_LONG) {
res = new Date(Bytes.toLong(bytes));
}
} else if (type == Type.BYTES) {
res = bytes;
} else if (type == Type.BIGDECIMAL) {
res = Bytes.toBigDecimal(bytes);
} else {
throw new IllegalArgumentException("mismatch class type");
}
return (T) res;
}
}

Some files were not shown because too many files have changed in this diff Show More