Compare commits

...

479 Commits

Author SHA1 Message Date
agapple
a7b12c7098 [maven-release-plugin] prepare release canal-1.1.3 2019-04-04 10:25:29 +08:00
agapple
2c320e8618 Merge pull request #1661 from rewerma/master
flatMessage模式下默认canal.mq.lingerMs=100
2019-04-04 10:08:34 +08:00
machey
d76d25df6c flatMessage模式下默认canal.mq.lingerMs=100 2019-04-04 00:14:27 +08:00
agapple
b95c0d03cf fixed issue #1660 ,support mysql8.0 caching_sha2_password auth 2019-04-03 23:42:41 +08:00
agapple
08af40221f Merge pull request #1658 from rewerma/master
adapter 日志打印修改
2019-04-03 12:12:56 +08:00
rewerma
d13c1eb17c Merge branch 'master' into master 2019-04-03 11:34:10 +08:00
agapple
82ed9db8ae fixed rocketmq partition out of range 2019-04-03 11:26:31 +08:00
agapple
d3195ac973 fixed issue #1626 , support alter table id first 2019-04-03 10:58:43 +08:00
agapple
15e29d276a fixed default xml config 2019-03-28 22:05:56 +08:00
agapple
d715ff64b4 fixed packet write batch 2019-03-28 21:52:51 +08:00
agapple
f63ce4ffe6 fixed mysql auth 2019-03-28 20:45:16 +08:00
agapple
a7f9a779ad fixed issue #1640 , support schema partition 2019-03-28 17:01:36 +08:00
agapple
39aeeb8c58 Merge pull request #1641 from wangsaner/master
修改canal.mq.partitionHash散列规则,使其支持同库下的不同表hash至相同分区
2019-03-28 16:58:31 +08:00
wangsaner
9826948ebb 修改canal.mq.partitionHash散列规则,
当指明pk级做分区(schema.table:pk1^pk2)时,由table.hashCode()^column.getValue().hashCode()改为database.hashCode()^column.getValue().hashCode(),从而支持同库下不同表hash至相同分区
2019-03-28 16:01:02 +08:00
wangsaner
d6ef66a281 Merge pull request #1 from alibaba/master
merge
2019-03-28 10:37:24 +08:00
agapple
97640b717b fixed compiler 2019-03-28 10:23:33 +08:00
agapple
20e7becfe9 fixed format 2019-03-28 10:19:53 +08:00
agapple
99b34ff632 Merge pull request #1639 from wangsaner/master
fixed issue #1636
2019-03-28 10:17:42 +08:00
wangsaner
739963acd0 fixed issue #1636
非flat模式,当MQ主题为多分区时,数据拆分只取了for循环里面的最后一份数据,
导致消费端只能消费到一个分区的数据
2019-03-27 21:57:04 +08:00
agapple
0ec4699149 fixed format & support Mysql Auth Switch Protocol 2019-03-27 11:54:40 +08:00
mcy
c101ae2280 Merge branch 'master' of https://github.com/rewerma/canal 2019-03-26 11:39:41 +08:00
mcy
a137deeaff 使用统一log 2019-03-26 11:29:52 +08:00
rewerma
c5be5b7a8a Merge pull request #40 from alibaba/master
merge
2019-03-26 00:43:11 +08:00
agapple
95bed6bcf6 fixed fastsql 2019-03-25 19:50:02 +08:00
agapple
661d119265 fixed issue #1625, mq dymaic topic match 2019-03-25 17:54:35 +08:00
agapple
08167c95c7 Merge pull request #1622 from rewerma/master
fix bugs
2019-03-22 18:14:14 +08:00
mcy
37c5168a7d code optimization 2019-03-22 14:49:50 +08:00
mcy
579fa72ae2 kafka consumer connector引入事务隔离级别 fix #1512 2019-03-22 13:44:02 +08:00
rewerma
844c019433 Merge pull request #39 from alibaba/master
merge
2019-03-22 09:10:43 +08:00
agapple
c94e3a0024 fixed default properties 2019-03-21 20:59:09 +08:00
agapple
f6ca489984 Merge pull request #1620 from rewerma/master
fix bugs
2019-03-21 20:51:10 +08:00
agapple
b2b69307f9 fixed rocketmq 2019-03-21 20:50:52 +08:00
agapple
531b535228 fixed bug 2019-03-21 20:26:00 +08:00
mcy
99cccd2113 fix #1608 2019-03-21 20:07:06 +08:00
rewerma
38d8c3d462 Merge branch 'master' into master 2019-03-21 19:42:07 +08:00
agapple
024ffc2aa2 fixed mq partition num priority 2019-03-21 19:39:21 +08:00
agapple
a81d2710ff fixed MQ partition 2019-03-21 19:27:39 +08:00
mcy
4bad0e88f8 增加日志配置 fix #1570 2019-03-21 19:04:33 +08:00
mcy
0181dfaa42 sql解析对case when 的支持
fix #1598
2019-03-21 18:35:58 +08:00
rewerma
0411d99e7b Merge pull request #37 from alibaba/master
merge
2019-03-21 18:07:41 +08:00
agapple
8fcdd7f798 Merge pull request #1619 from rewerma/master
fix #1608
2019-03-21 17:58:57 +08:00
mcy
b0f4e4ee78 fix #1608 2019-03-21 17:55:35 +08:00
agapple
d448455ebf Merge pull request #1613 from KangZhiDong/master
set utf8mb4 to support emoji
2019-03-21 16:49:44 +08:00
agapple
e07fdb6426 Update README.md 2019-03-21 16:46:42 +08:00
agapple
38336b249e fixed travis 2019-03-21 16:36:41 +08:00
agapple
e7b81cd83a fixed mvn wrapper 2019-03-21 16:30:05 +08:00
agapple
2810e7e815 add mvnw 2019-03-21 16:20:13 +08:00
agapple
024986a195 fixed travlis.yml 2019-03-21 16:16:31 +08:00
kangzhidong
9b2ad0eafd set utf8mb4 to support emoji 2019-03-20 16:29:53 +08:00
康智冬
03d46f150f Merge pull request #2 from alibaba/master
update
2019-03-20 16:21:38 +08:00
agapple
b488ca02d8 fixed travlis 2019-03-20 13:55:29 +08:00
agapple
17a6ba9c9c fixed travlis 2019-03-20 13:44:29 +08:00
agapple
3b618740d4 Merge pull request #1607 from rewerma/master
ES同步新增父子文档索引适配
2019-03-20 13:35:25 +08:00
mcy
06bfcda275 code optimization 2019-03-20 10:41:51 +08:00
mcy
5821c1f606 modify 2019-03-20 09:10:15 +08:00
mcy
a2360efff9 增加对sql字段 `` 的支持
fix #1559
2019-03-19 19:13:15 +08:00
mcy
51c469aa6f es etl 主键类型bug fix #1559 2019-03-19 17:54:11 +08:00
mcy
6b3bf31404 adapter代码整理优化 2019-03-19 17:49:00 +08:00
mcy
941ed7f60d Merge branch 'feature/es2' 2019-03-18 09:06:58 +08:00
rewerma
9db2bd291b Merge pull request #36 from alibaba/master
merge
2019-03-18 09:05:47 +08:00
agapple
95b26c4d1d fixed #1433, optimzier code 2019-03-17 10:35:16 +08:00
agapple
b114e0cb15 Merge pull request #1581 from alexinea/master
CanalSharp has transferred to .NET Core Community
2019-03-17 10:30:03 +08:00
agapple
5c96bf282e Merge pull request #1590 from rewerma/master
fix #1585
2019-03-17 10:29:12 +08:00
agapple
6d280d1048 Merge pull request #1597 from KangZhiDong/master
fix the error log does not contain table name
2019-03-17 10:28:47 +08:00
agapple
cf4db1625d Merge pull request #1605 from lizhyocx/master
解决单核服务器无法订阅数据的bug
2019-03-17 10:27:50 +08:00
lizhyocx
df98fec945 解决单核服务器无法订阅数据的bug
解决单核服务器c中anal.instance.parser.parallel = true时无法订阅数据的bug
2019-03-15 17:11:14 +08:00
mcy
e03705e211 es父子文档索引ETL支持 2019-03-13 17:19:08 +08:00
mcy
4df60a0fb5 es增加父子文档索引同步适配 2019-03-13 16:57:23 +08:00
kangzhidong
078673c6a9 fix the error log does not contain table name 2019-03-13 16:55:38 +08:00
mcy
4bd91a1079 fix bug 2019-03-13 14:36:27 +08:00
mcy
05ad671cc1 es增加relations配置
配置monitor yml加载使用通用加载类
2019-03-13 14:34:31 +08:00
mcy
43045bc672 use system time zone for es datetime field formatter 2019-03-13 10:01:16 +08:00
mcy
971c9a88c2 modify 2019-03-12 17:07:33 +08:00
rewerma
7b6e482827 Merge pull request #35 from alibaba/master
merge
2019-03-12 16:58:05 +08:00
mcy
ad39559f8c fix #1585 2019-03-12 16:56:59 +08:00
AlexLEWIS
2707c12570 CanalSharp has transferred to .NET Core Community
CanalSharp has transferred to .NET Core Community(NCC) and became a member project of NCC.
2019-03-11 14:47:14 +08:00
康智冬
7dab5ac6c2 Merge pull request #1 from alibaba/master
update
2019-03-08 10:05:20 +08:00
agapple
9d4b61f083 Merge pull request #1567 from dragontree101/miss_producergroup_properties
producerGroup配置没有读取
2019-03-06 11:07:15 +08:00
dragonlong1986
a34f9ae97b Merge branch 'master' into miss_producergroup_properties 2019-03-05 17:44:35 +08:00
dragonlong1986
64ca62b986 update 2019-03-05 17:39:56 +08:00
agapple
14c1af4c80 Merge pull request #1542 from rewerma/master
fixed #1538
2019-03-04 14:13:14 +08:00
agapple
7436d9f06f Merge pull request #1550 from lee123lee123/master
Issues #1460  修改tsdb中同步数据带表情字符错误问题
2019-03-04 14:12:33 +08:00
agapple
c4599df1c2 Merge pull request #1560 from dragontree101/doc_mistake_key
key改成大写,小写不会生效
2019-03-04 14:07:22 +08:00
dragonlong1986
7abf50f233 key改成大写,小写不会生效 2019-03-02 10:25:19 +08:00
machey
1781381187 modify 2019-03-01 00:19:47 +08:00
machey
5d4210bd22 modify 2019-02-28 23:31:07 +08:00
machey
9888fae559 负时区的bug 2019-02-28 23:22:02 +08:00
rewerma
81ab7e9783 Merge branch 'master' into master 2019-02-28 19:37:27 +08:00
mcy
b891afa50e 增加时区判断 2019-02-28 19:28:17 +08:00
lee123lee123
8187d7c5df Issues #1460 修改utf8数据带表情字符错误 2019-02-28 19:04:14 +08:00
mcy
e7f069850f fixed #1538 2019-02-28 17:29:06 +08:00
agapple
d377a92a5d fixed issue #1548, NPE 2019-02-28 10:52:28 +08:00
agapple
f664af1af4 fixed issue #1546 , support drop database 2019-02-28 10:47:42 +08:00
agapple
5e2eabb3c2 Merge pull request #1544 from finefuture/MQ-fix-pr
fix issue 1532
2019-02-28 10:32:41 +08:00
agapple
f2368e5469 Merge pull request #1540 from wurongjun/master
修复当适配器mapping文件修改时,无法生效的问题
2019-02-28 10:32:07 +08:00
TomlongTK
fd4c8db92b fix issue 1532
因为binlog日志同表名大小写的原因导致kafka的日志恢复并resize,报错之后引起集群崩溃
现在修改为投递消息的topic不区分表名大小写
2019-02-26 14:23:29 +08:00
mcy
f21abc13af fixed #1538 2019-02-26 10:23:38 +08:00
wurongjun@duiba.com.cn
ae474d67ad 修复当适配器mapping文件修改时,无法生效的问题 2019-02-25 17:53:30 +08:00
bigbro
0c4711ab62 Merge pull request #2 from alibaba/master
合并原项目最新代码
2019-02-25 17:51:19 +08:00
agapple
072420c32b fixed 2019-02-25 15:18:04 +08:00
agapple
1473a4a6b5 Merge pull request #1523 from rewerma/master
es adapter新增upsert属性
2019-02-25 14:50:58 +08:00
agapple
0fff514876 Merge pull request #1533 from DaiZZZZZ/master
CanalParameter添加parallel参数,用来配置canalinstance
2019-02-25 14:45:54 +08:00
agapple
4017de5102 Merge pull request #1526 from jiuchengspotorg/master
adapter rdb无法同步ddl更新问题修正
2019-02-25 14:15:47 +08:00
mcy
fcc1fc78ae fix #1521 2019-02-22 17:09:50 +08:00
mcy
17efc2ada0 es 批量提交修改 2019-02-22 16:56:58 +08:00
zhikuodai
a68bd28852 CanalParameter类添加parallel参数 2019-02-21 22:04:06 +08:00
daizzzzz
1de8b27da1 Merge pull request #3 from alibaba/master
merge
2019-02-21 17:25:55 +08:00
mcy
01dc8ce2b8 YmlConfigBinder代码整理 2019-02-20 15:00:48 +08:00
jiucheng
f7e490f7a0 1、adapter/rdb,从库表字段(varchar,char,text,longtext)非空约束时,无法同步问题修正
2、主库存在0000-00-00 00:00:00时间时,从库表字段是非空约束时,无法同步问题修正
2019-02-20 10:10:14 +08:00
jiucheng
8cadd585c6 adapter rdb无法同步ddl更新问题修正
adapter/support/MessageUtil.java
2019-02-20 09:49:15 +08:00
mcy
57c3226e56 Merge branch 'feature/es2' 2019-02-19 15:05:59 +08:00
rewerma
79a3961da1 Merge pull request #34 from alibaba/master
merge
2019-02-19 15:05:34 +08:00
agapple
62338a945e Merge pull request #1496 from rewerma/master
远程配置代码整理
2019-02-18 21:08:02 +08:00
agapple
e07db7b178 Merge pull request #1507 from i404/master
修复 FlatMessage  Hash 到不正确的分区的Bug。
2019-02-18 21:05:03 +08:00
mcy
05cc627b5d fix rdb etl bug 2019-02-15 11:39:26 +08:00
i404
44beaf1916 修复当 canal.mq.flatMessage = true 时,MQMessageUtils::messagePartition 数据分区错误的 bug 2019-02-14 18:26:01 +08:00
mcy
476244b26d modify 2019-02-14 15:41:27 +08:00
mcy
cfdf509f9a 增加对pk的支持 2019-02-14 15:29:27 +08:00
mcy
c14ed724ff update中增加upsert判断 2019-02-13 15:59:46 +08:00
mcy
5a4cfeab46 Merge branch 'master' into feature/es2
# Conflicts:
#	client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/support/ESTemplate.java
2019-02-13 15:51:24 +08:00
mcy
947cf130ab fixed #1504 2019-02-13 15:50:11 +08:00
mcy
0ef21e4155 代码整理 2019-02-13 15:47:48 +08:00
mcy
83b760494d ES adapter代码重新整理
新增upsert属性
2019-02-13 15:19:09 +08:00
mcy
aba8df699c Merge branch 'master' into feature/es2
# Conflicts:
#	client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/SchemaItem.java
2019-02-12 17:21:42 +08:00
rewer
0c5f5c128f 整理代码 2019-02-07 13:49:35 +08:00
mcy
ad62275cd7 增加kafka自定义properties 2019-02-05 19:20:36 +08:00
mcy
f6d03bcb37 fixed #1490 2019-02-05 17:48:08 +08:00
mcy
5284aa9793 fix bugs 2019-02-01 16:20:53 +08:00
mcy
10276e254a mq模式下adapter配置增加groupId属性对应 2019-02-01 15:18:16 +08:00
mcy
597a08ac37 通过groupId过滤对应的adapter配置 2019-02-01 13:51:49 +08:00
mcy
8211ef35e6 修改yml配置读取绑定,遵循spring yml加载方式,支持environment级占位符替换 2019-02-01 10:11:52 +08:00
mcy
a20960834b 远程配置代码整理 2019-01-31 09:45:52 +08:00
rewerma
7b6b332463 Merge pull request #33 from alibaba/master
merge
2019-01-31 09:03:37 +08:00
agapple
43f869599d Merge pull request #1484 from liulangwa/master
捕获"rs = query("select @@global.binlog_checksum")" 结果rs为null的异常
2019-01-30 11:45:33 +08:00
agapple
e3d05f6201 Merge pull request #1482 from wurongjun/master
修复从数据库加载配置写入文件时因目录不存在出现的问题
2019-01-30 11:43:47 +08:00
codebee
032301102c 捕获"rs = query("select @@global.binlog_checksum")" 结果rs为null的异常 2019-01-29 17:10:26 +08:00
wurongjun@duiba.com.cn
bb64d84298 修复从数据库加载配置写入文件时因目录不存在出现的问题 2019-01-29 16:10:26 +08:00
bigbro
ccd39e9d62 Merge pull request #1 from alibaba/master
update
2019-01-29 16:03:43 +08:00
agapple
32031da9db Merge pull request #1475 from rewerma/master
整理远程配置, 统一扩展接口
2019-01-29 10:13:03 +08:00
mcy
9a2e890f57 整理adapter端远程配置, 统一扩展接口 2019-01-25 18:50:30 +08:00
mcy
8f76b33214 解析配置修改 2019-01-25 17:51:38 +08:00
mcy
a7467539c7 整理server端远程配置, 统一扩展接口 2019-01-25 17:50:50 +08:00
agapple
db5495d158 Merge pull request #1464 from KangZhiDong/master
修改注释
2019-01-25 10:41:40 +08:00
kangzhidong
b04d948064 修改注释 2019-01-23 11:58:07 +08:00
agapple
5f8eb4d261 Merge pull request #1456 from rewerma/master
新增kafka消息投递事务
2019-01-21 23:45:13 +08:00
mcy
197ca67c8b 测试调整 2019-01-21 09:56:35 +08:00
machey
851637611f modify 2019-01-20 15:02:06 +08:00
machey
4ebf17f57b 代码调整 2019-01-20 14:30:20 +08:00
machey
2f214ce3a6 采用事务批量异步投递kafka flatMessage
fixed #1454
2019-01-20 14:20:06 +08:00
agapple
8d4fbb395b fixed issue #1451 , fixed NPE 2019-01-18 11:00:02 +08:00
agapple
d935c39648 Merge pull request #1449 from wdlily/master
fixed a word
2019-01-18 10:24:01 +08:00
wdlily
6e0e3fbef1 fixed a word
change '租' to '组'
2019-01-17 15:52:24 +08:00
rewerma
cbe88af2e3 Merge pull request #1442 from rewerma/master
fixed #1430
2019-01-17 15:18:40 +08:00
mcy
ba9d9dea6d fixed #1433 2019-01-17 11:27:12 +08:00
mcy
2d88d803ee merge #1440 and fixed #1430 2019-01-17 10:52:27 +08:00
rewerma
ff1fef1eb6 Merge pull request #32 from alibaba/master
merge
2019-01-17 10:48:23 +08:00
rewerma
4beb1e6855 Merge branch 'master' into master 2019-01-17 10:48:14 +08:00
rewerma
d2bbe9b061 Merge pull request #1440 from wurongjun/master
fix #1430
2019-01-17 10:46:30 +08:00
rewerma
171bedf488 Merge pull request #31 from alibaba/master
merge
2019-01-17 10:43:49 +08:00
mcy
e387cfdee5 fixed #1430 2019-01-17 10:22:08 +08:00
wurongjun@duiba.com.cn
47d7914304 停机时清空zk客户端信息,修复重启时报出的zk连接已关闭的异常 2019-01-16 17:28:51 +08:00
agapple
e4177ab0ce Merge pull request #1419 from rewerma/master
specified mq dynamic topic
2019-01-16 10:27:25 +08:00
mcy
739f3b4fa1 fixed #1433 2019-01-15 18:39:32 +08:00
mcy
60441d8b31 fixed #1396 2019-01-15 18:20:05 +08:00
mcy
ca41be8412 fixed #1420 2019-01-15 11:38:00 +08:00
mcy
18b304f99d specified mq dynamic topic 2019-01-14 14:25:47 +08:00
agapple
7f9dce4b77 Merge pull request #1418 from rewerma/master
fixed #1416
2019-01-14 13:10:32 +08:00
mcy
f26adff7c7 fixed #1416 2019-01-14 11:16:33 +08:00
mcy
77646c0185 拼写错误 2019-01-14 10:58:33 +08:00
agapple
7e4912cd2e Merge pull request #1413 from wingerx/master
获取TableMeta信息时,增加一次retry操作
2019-01-12 11:26:30 +08:00
agapple
70a4f0363d Merge pull request #1412 from rewerma/master
fixed #1408 fixed # 1403
2019-01-12 11:26:11 +08:00
winger
725960d46e 获取TableMeta信息时,增加一次retry操作 2019-01-11 14:00:15 +08:00
mcy
b00d347eb0 rdb adapter 增加 truncate dml适配 fixed #1408
fixed #1403
2019-01-11 13:40:37 +08:00
rewerma
92666b41e8 Merge pull request #30 from alibaba/master
merge
2019-01-11 12:59:01 +08:00
agapple
f1753e79f9 Merge pull request #1407 from lileibuaa/master
fix filter transaction entry logic in EntryEventSink
2019-01-11 10:29:06 +08:00
lileibuaa
f1a998b2d5 fix filter transaction entry logic in EntryEventSink 2019-01-10 21:16:25 +08:00
agapple
03e6662ce5 fixed mq 2019-01-08 23:57:16 +08:00
agapple
b845a67666 Merge pull request #1391 from rewerma/master
fixed 1390
2019-01-08 23:42:57 +08:00
mcy
690222bde8 dynamic topic 正则注释 2019-01-08 11:28:29 +08:00
mcy
63ad80c8c4 fixed 1390 2019-01-08 10:39:32 +08:00
agapple
de501c85a1 fixed issue #1374 , support queryLogEvent partition 2019-01-07 13:39:04 +08:00
agapple
670b7830a5 Merge pull request #1383 from rewerma/master
增加adapter远程配置
2019-01-07 13:31:01 +08:00
agapple
fdf150bd24 fixed issue #1341 2019-01-07 13:28:48 +08:00
agapple
c66b6a0f0f fixed charset 2019-01-07 12:59:33 +08:00
machey
a91aebafc2 modify 2019-01-06 21:08:17 +08:00
machey
35fcd56822 Merge branch 'feature/mgr'
# Conflicts:
#	client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/monitor/RdbConfigMonitor.java
2019-01-05 01:53:42 +08:00
machey
a63e982115 添加注释 2019-01-05 01:51:43 +08:00
machey
4bdf3d766f modify 2019-01-05 01:44:04 +08:00
machey
779ebad890 adapter 远程配置
修改rdb热加载配置的问题
2019-01-05 01:42:27 +08:00
agapple
b348260288 fixed issue #1363 , date format 2019-01-03 11:48:33 +08:00
agapple
3011c3292b Merge pull request #1362 from rewerma/master
fixed #1359
2019-01-02 22:42:22 +08:00
mcy
94b8fdbbf4 动态topic的xml配置 2019-01-02 13:28:27 +08:00
mcy
1dd9934f9c fixed #1359 2019-01-02 13:23:32 +08:00
mcy
3dec7c50ce commit 2019-01-02 11:05:46 +08:00
machey
ecd9ababa1 modify 2019-01-01 17:55:23 +08:00
machey
b0eaa156cc 增加adapter远程配置 2019-01-01 17:33:01 +08:00
agapple
a70178afef Merge pull request #1357 from rewerma/master
canal server配置统一数据库管理监控
2018-12-31 12:16:21 +08:00
machey
114c3bd020 modify 2018-12-30 17:16:47 +08:00
machey
f9e19d46c4 增加注释 2018-12-30 17:08:11 +08:00
machey
661752096c 增加注释 2018-12-30 17:04:15 +08:00
machey
100b43a5db Merge branch 'feature/mgr' 2018-12-30 16:50:32 +08:00
rewerma
5e116de264 Merge pull request #29 from alibaba/master
merge
2018-12-30 16:50:05 +08:00
machey
a1091368fb modify 2018-12-30 16:47:35 +08:00
machey
71b7c0b868 canal server增加数据库远程配置 2018-12-30 16:44:35 +08:00
mcy
7c79714528 增加远程db config配置 2018-12-29 20:21:03 +08:00
agapple
8d84521767 Merge pull request #1355 from rewerma/master
adapter 分组间线程统一抛异常
2018-12-28 23:06:08 +08:00
agapple
14ba358525 fixed dymaic topic 2018-12-28 18:35:50 +08:00
mcy
7ea1468a8d adapter 分组间线程统一抛异常 2018-12-28 17:30:04 +08:00
agapple
f72ac9a3bb fixed adapter throw exception 2018-12-28 16:55:44 +08:00
agapple
f100aeda39 Merge pull request #1345 from rewerma/master
fixed 1341
2018-12-28 15:22:20 +08:00
agapple
953a0d0583 Merge pull request #1349 from mujiansu/master
fix bat syntax error
2018-12-28 15:20:05 +08:00
agapple
2afe7debda Merge pull request #1305 from wingerx/master
修复同时开启gtid与tsdb 时gtid 属性丢失的问题
2018-12-28 15:13:47 +08:00
agapple
d8737a5228 fixed issue #1343, support charset 255 2018-12-28 13:46:50 +08:00
mcy
150a13824b 修改 kafka 异步发送 flat message 2018-12-27 17:52:33 +08:00
JasonMu
830e149d0e fix bat syntax error 2018-12-27 16:41:15 +08:00
mcy
d9e259dde4 增加支持按库名、表名动态发送topic支持 2018-12-27 15:40:04 +08:00
mcy
9e6adff942 修复adapter无法回退的问题,fixed 1341
修复HBase断连的问题
2018-12-26 15:24:30 +08:00
winger
82a78546ab Merge remote-tracking branch 'upstream/master' 2018-12-26 15:11:17 +08:00
winger
19c35c0b08 修复:当mysql 发生过purged gtid时,同时开启tsdb与gtid后,gtid属性丢失问题 2018-12-26 15:10:57 +08:00
agapple
ade2a54369 Merge pull request #1340 from rewerma/master
fixed #1338
2018-12-25 22:42:25 +08:00
machey
dffbc437a6 fixed #1338 2018-12-25 22:27:27 +08:00
mcy
b5697a366a merge 2018-12-23 18:24:04 +08:00
rewerma
0469d28c05 Merge pull request #28 from alibaba/master
merge
2018-12-23 18:20:43 +08:00
rewerma
d5f44dd76f Merge branch 'master' into master 2018-12-23 18:20:33 +08:00
mcy
354d64069f adapter增加同步异常重试 2018-12-23 18:16:47 +08:00
winger
3465866278 修复开启gtid时,同时开启tsdb后gtid 属性丢失的问题 2018-12-21 15:18:07 +08:00
agapple
840148fbf6 Merge pull request #1326 from rewerma/master
类型转换问题 fixed #1313
2018-12-21 15:10:25 +08:00
mcy
34b5f9c927 modify 2018-12-20 19:53:30 +08:00
mcy
75ee65f9e8 类型转换问题 fixed #1313 2018-12-20 19:45:57 +08:00
agapple
035ef26542 fixed issue #1306 , support MySqlOrderingExpr 2018-12-19 11:43:49 +08:00
agapple
4ef45b9a1d Merge pull request #1310 from panjianping/master
添加 KafkaOffsetCanalConnector 使用示例
2018-12-18 16:51:50 +08:00
panjianping
990fa9498a 添加 KafkaOffsetCanalConnector 使用示例 2018-12-18 16:46:19 +08:00
agapple
276a2b988a Merge pull request #1308 from panjianping/master
添加带kafka消息offset的Connector
2018-12-18 16:15:01 +08:00
panjianping
ef72cddfe8 添加带kafka消息offset的Connector 2018-12-18 11:54:41 +08:00
winger
d8d3246e30 Merge remote-tracking branch 'upstream/master' 2018-12-17 16:15:18 +08:00
winger
bba800eb96 修复开启gtid时,同时开启tsdb后gtid 属性丢失的问题 2018-12-17 16:14:55 +08:00
agapple
ff71bf515a Merge pull request #1300 from gsfeng179/master
Update README.md
2018-12-17 12:52:58 +08:00
agapple
6ef48591a1 Merge pull request #1299 from rewerma/master
FlatMessage没有默认构造方法json反序列化的问题
2018-12-17 12:52:37 +08:00
gsfeng
a7526dbf4a Update README.md
FAQ link error
2018-12-17 10:18:06 +08:00
mcy
51d94efc9c dml类整理 2018-12-17 10:13:04 +08:00
mcy
833cc55e26 FlatMessage没有默认构造方法json反序列化的问题
Dml类增加isDdl属性
2018-12-17 09:47:09 +08:00
agapple
689366248d Merge pull request #1289 from rewerma/master
rdb adapter增加mysql镜像db(schema)同步
2018-12-14 18:34:52 +08:00
mcy
3acdffb297 mirror schema 完成测试 2018-12-14 14:58:15 +08:00
mcy
fbf249580f mirror db 2018-12-14 11:30:41 +08:00
agapple
d2e3e19831 fixed issue #1282 , NPE 2018-12-13 12:52:59 +08:00
agapple
0523b68c26 fixed parse error 2018-12-12 10:48:22 +08:00
agapple
995a33a645 Merge pull request #1278 from WithLin/master
Update AddressUtils.java
2018-12-12 10:44:05 +08:00
mcy
5c4b1f6e63 modify 2018-12-12 10:11:06 +08:00
XiaoFu
ba5865683f Update AddressUtils.java 2018-12-12 10:03:49 +08:00
mcy
89a4943349 Merge branch 'master' into feature/rdb 2018-12-12 09:05:59 +08:00
agapple
35e0e882c1 fixed hash mode 2018-12-11 20:41:21 +08:00
agapple
0197482618 fixed partitionHash format 2018-12-11 20:17:08 +08:00
agapple
72bad4d54c Merge pull request #1272 from rewerma/master
去除的多余的依赖
2018-12-11 17:03:07 +08:00
mcy
48de904ee1 去掉 #1266 中增加的 protocol 的多余的依赖 2018-12-11 14:43:13 +08:00
rewerma
f6a931d8c6 Merge pull request #27 from alibaba/master
merge
2018-12-11 14:15:33 +08:00
agapple
277a273ebc fixed pull #1266 , optimizer code 2018-12-11 13:40:32 +08:00
agapple
bd5110b951 Merge pull request #1266 from rewerma/master
增加mq 分区规则配置正则表达式
2018-12-11 12:09:36 +08:00
machey
aeecf87490 去掉对before的判断,分区发送的表pk不能修改 2018-12-11 00:09:12 +08:00
machey
eac3f5ce5f 修改convert时设置pk 2018-12-10 23:54:07 +08:00
mcy
706ee88914 pattern表达式调整
pk缓存
2018-12-10 21:28:09 +08:00
agapple
7c63e81e01 fixed issue #1256, rds alibaba_rds_row_id 2018-12-10 14:42:28 +08:00
mcy
99a1123e01 整理代码 2018-12-10 09:52:48 +08:00
mcy
24d898d87a rdb 全schema同步 2018-12-10 09:25:40 +08:00
machey
5650b56bed 打印错误日志 2018-12-10 01:10:04 +08:00
machey
547cbf82c8 对原生message发送mq的分区支持 2018-12-10 01:03:18 +08:00
machey
03ee9c7ecf 根据主键Hash增加正则表达式支持,pk通配符指定自动获取支持 2018-12-10 00:09:49 +08:00
rewerma
ac738dae05 Merge pull request #26 from alibaba/master
merge
2018-12-09 22:06:29 +08:00
agapple
dda8081984 Merge pull request #1263 from tankilo/master
Linux启动脚本格式是DOS,需要转换成Unix格式
2018-12-08 13:26:52 +08:00
tankilo
f0fc37c43b switch file format from DOS to UNIX 2018-12-07 14:54:45 +08:00
tankilo
5a99de1fa9 switch file format from DOS to UNIX 2018-12-07 14:54:06 +08:00
agapple
ce956c980d add github template 2018-12-06 18:48:17 +08:00
agapple
c045dc244e optimizer filter 2018-12-06 10:50:12 +08:00
agapple
1b11aa1d21 fixed issue #1220, 解决一下no alive canal server重试过于频繁 2018-12-06 10:31:13 +08:00
rewerma
dac642f744 Merge pull request #25 from alibaba/master
merge
2018-12-03 17:55:32 +08:00
agapple
19e164b599 Merge pull request #1232 from rewerma/master
fixed #1229
2018-11-30 13:45:10 +08:00
mcy
339ecdd817 fixed #1229 2018-11-29 17:38:13 +08:00
rewerma
5fe7ae1613 Merge pull request #1228 from rewerma/master
rdb 关键字转译符过滤 fixed  #1224
2018-11-29 15:42:19 +08:00
mcy
65f2695963 rdb 关键字转译符过滤 2018-11-29 15:39:08 +08:00
mcy
cf5e42442b mysql jdbc依赖移到外部pom 2018-11-29 13:42:37 +08:00
agapple
e7b678d5e0 Merge pull request #1218 from rewerma/master
client打fat包排除其他包
2018-11-29 13:27:02 +08:00
mcy
f04447837c client打fat包排除其他 2018-11-29 10:21:27 +08:00
rewerma
b7d2ad2e18 Merge pull request #1208 from rewerma/master
fixed #1201
2018-11-27 22:40:47 +08:00
machey
f1710f1c7f fixed #1201 2018-11-27 22:39:49 +08:00
agapple
37094447b1 Merge pull request #1205 from rewerma/master
移除adapter pom中的parent fixed #1203
2018-11-27 19:41:42 +08:00
mcy
b97d377d83 移除adapter pom中的parent fixed #1203 2018-11-27 19:38:30 +08:00
agapple
46b6528273 fixed tablemeta check 2018-11-27 19:33:25 +08:00
agapple
6c3b3acde0 Merge pull request #1202 from DinoZhang/DinoZhang-patch-1
fix Datasource named ${DS_PROMETHEUS} was not found
2018-11-27 19:25:59 +08:00
DinoZhang
91b036a4fc fix Datasource named ${DS_PROMETHEUS} was not found 2018-11-27 17:34:08 +08:00
DinoZhang
67cd0e5fca Merge pull request #2 from alibaba/master
merge master
2018-11-27 17:10:57 +08:00
rewerma
9dfa5da7bc Merge pull request #24 from alibaba/master
merge
2018-11-27 11:53:47 +08:00
agapple
9bda60bf89 fixed pom 2018-11-26 14:29:13 +08:00
agapple
9aaf20fb5e [maven-release-plugin] prepare for next development iteration 2018-11-26 13:22:04 +08:00
agapple
8496ceaf15 [maven-release-plugin] prepare release canal-1.1.2 2018-11-26 13:21:51 +08:00
agapple
cf23e0ffea fixed javadoc 2018-11-26 13:11:24 +08:00
agapple
4e9e3f5abe fixed issue #1192 , NPE test 2018-11-25 21:17:34 +08:00
agapple
cbab3fb561 fixed issue #1168, support filter with compareTableMetaDbAndMemory 2018-11-25 21:01:19 +08:00
agapple
f7bf4ac6ff fixed log test 2018-11-25 18:33:34 +08:00
agapple
df4c66b9e2 Merge pull request #1193 from woshijay7788/feature_kafka_config
add max.request.size config
2018-11-25 11:55:51 +08:00
agapple
a6ee87281a Merge pull request #1194 from guixincui/master
修改抽象引用
2018-11-25 11:55:00 +08:00
guixincui
90dab6831b 修改接口引用 2018-11-24 23:40:18 +08:00
gl
839d5415fc add max.request.size config 2018-11-24 22:48:54 +08:00
agapple
29f03bf398 fixed batchSize calc 2018-11-24 15:34:39 +08:00
agapple
ec235f3efd fixed rocketmq ak/sk 2018-11-23 09:56:55 +08:00
rewerma
3b25b3b342 Merge pull request #23 from alibaba/master
merge
2018-11-22 09:04:24 +08:00
agapple
aa89453bf6 fixed adapter 2018-11-21 22:49:22 +08:00
agapple
cc97f3b331 Merge pull request #1171 from rewerma/master
adapter 分批同步
2018-11-21 22:09:33 +08:00
agapple
89ec3037d4 Merge pull request #1173 from RickJou/master
等号两边都有空格
2018-11-21 21:48:15 +08:00
mcy
e711f1a091 rdb 并行提交 2018-11-21 16:57:02 +08:00
Alan
ad18e0335d 等号两边都有空格
格式保持一致,看起来更像大厂出品  ^_^
2018-11-21 16:31:46 +08:00
mcy
67cc73b8e2 adapter 配置修改 2018-11-21 14:26:19 +08:00
agapple
638952fad1 fixed binlog seek & aliyun ak/sk 2018-11-21 13:51:34 +08:00
mcy
56ec66f90e adapter 分批同步 2018-11-21 13:46:30 +08:00
agapple
c0bbe530f1 Merge pull request #1169 from ymwneu/supportRocketMQ
[ISSUE#1109]Support aliyun RocketMQ
2018-11-21 12:51:31 +08:00
maowei.ymw
4bfc476e08 Modify rocketMQ related pom 2018-11-21 10:44:58 +08:00
rewerma
5d71d50d4e Merge pull request #22 from alibaba/master
merge
2018-11-20 20:08:11 +08:00
agapple
26700f3005 fixed batch sync dml 2018-11-20 19:01:07 +08:00
agapple
a7be9c75f9 fixed mysql rotate checksum 2018-11-20 18:29:01 +08:00
agapple
b6f1e0d13c fixed rocketmq ack npe 2018-11-20 17:14:29 +08:00
maowei.ymw
50630cc934 Support client connect rocketmq in aliyun 2018-11-19 14:33:59 +08:00
agapple
43db5bec97 Merge pull request #1155 from rewerma/master
es readme.md
2018-11-18 12:51:29 +08:00
mcy
affd598470 es readme.md 2018-11-17 21:18:48 +08:00
agapple
178bea07a8 Merge pull request #1154 from rewerma/master
adapter 配置修改动态加载
2018-11-17 15:29:00 +08:00
mcy
fcdf95b5ec rdb 批量提交超时提交 2018-11-17 04:37:46 +08:00
mcy
991418ce6e 修改rdb批量提交分批的问题 2018-11-16 22:53:10 +08:00
mcy
d34bee3359 adapter 配置修改动态加载 2018-11-16 18:07:32 +08:00
agapple
0a3c1645e8 Merge pull request #1153 from rewerma/master
rdb 批量提交优化
2018-11-16 14:07:23 +08:00
mcy
9dc4d78b3b fix 2018-11-16 13:24:36 +08:00
mcy
bc056288d4 rdb 批量提交优化 2018-11-16 13:12:50 +08:00
agapple
8aea31232b Merge pull request #1151 from rewerma/master
将oracle mssql pgsql 的jdbc打包进lib
2018-11-16 10:16:26 +08:00
machey
fc6c871382 Merge branch 'master' into feature/mcy
# Conflicts:
#	client-adapter/rdb/pom.xml
2018-11-16 00:06:56 +08:00
machey
14c0e4535a 将oracle mssql pgsql 的jdbc打包进lib 2018-11-15 23:59:16 +08:00
mcy
7ee7ba0e05 rdb 批量提交 2018-11-15 22:47:08 +08:00
agapple
7992b883d2 fixed binlog checksum 2018-11-15 19:40:23 +08:00
agapple
5bc0c3f06f Merge pull request #1147 from rewerma/master
rdb 批量并行提交
2018-11-15 19:28:29 +08:00
mcy
b457ecd387 adapter 主配置application.yml动态加载 2018-11-15 17:09:53 +08:00
mcy
997646b2db 指定是否按主键hash并行同步, 否则按表名hash表间并行同步 2018-11-15 10:39:34 +08:00
mcy
915849b83a 预留sync(MappingConfig config, List<Dml> dmlList)方法
pk hash以old pk值为准若存在
2018-11-14 20:26:38 +08:00
agapple
129118b483 Merge pull request #1149 from spccold/master
filter views when dumpTableMeta
2018-11-14 19:50:41 +08:00
wuwo
aa68408c53 filter views when dumpTableMeta 2018-11-14 19:21:34 +08:00
mcy
d396a0b65c rdb 批量提交 2018-11-14 18:21:50 +08:00
mcy
827b872cf8 rdb 批量并行提交 2018-11-14 18:05:55 +08:00
agapple
6f3cae5ddf Merge pull request #1143 from rewerma/master
配置整理
2018-11-14 16:54:29 +08:00
mcy
31bf92ec8e 配置整理 2018-11-14 11:49:22 +08:00
mcy
68d8aee6c3 配置整理 2018-11-14 11:32:01 +08:00
mcy
c376f0afd2 配置中增加 canal.mq.acks canal.mq.compressionType 2018-11-14 11:14:32 +08:00
mcy
60e5554125 修改bootstrapServers为mqServers 2018-11-14 11:00:55 +08:00
mcy
241c43a894 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java
2018-11-14 10:50:11 +08:00
rewerma
fecc112196 Merge pull request #21 from alibaba/master
merge
2018-11-14 10:48:04 +08:00
agapple
dd04577994 Merge pull request #1142 from spccold/master
let tsdbSnapshotInterval config work correctly
2018-11-13 22:04:16 +08:00
agapple
51a299259b Merge pull request #1141 from Renkai/compression_type
Kafka增加参数acks和compressionType
2018-11-13 22:03:00 +08:00
wuwo
0a0a39880c Merge remote-tracking branch 'upstream/master' 2018-11-13 20:54:45 +08:00
wuwo
5ad9cf3f16 let tsdbSnapshotInterval config work correctly 2018-11-13 20:54:36 +08:00
mcy
d29fe5b04e 去掉调试代码 2018-11-13 20:01:20 +08:00
agapple
20062d861d fixed test code 2018-11-13 19:59:18 +08:00
agapple
73de8b32a9 Merge pull request #1140 from rewerma/master
adapter plugin
2018-11-13 19:51:36 +08:00
Renkai
8af1301062 Kafka增加参数acks和compressionType 2018-11-13 18:29:25 +08:00
mcy
8c3ade2d50 外部adapter plugin文件夹 2018-11-13 17:40:12 +08:00
rewerma
752a3cd66a Merge pull request #20 from alibaba/master
merge
2018-11-13 14:13:41 +08:00
agapple
154ba422f2 fixed plugin 2018-11-13 14:10:48 +08:00
agapple
03a9522191 fixed event 2018-11-13 13:41:19 +08:00
agapple
c4947cad3c Merge pull request #1133 from spccold/master
fix MemoryTableMeta#processTableElement bug
2018-11-13 11:34:15 +08:00
wuwo
3f8cdafea3 fix MemoryTableMeta#processTableElement bug 2018-11-13 11:14:37 +08:00
agapple
fcaf8e88f8 Merge pull request #1132 from rewerma/master
rdb adapter整理
2018-11-13 09:18:27 +08:00
mcy
e7ab25f819 增加rdb adapter说明 2018-11-12 18:26:34 +08:00
mcy
b45a27e231 db同步代码整理 2018-11-12 18:01:52 +08:00
mcy
2e5b7b89af 简化adapter配置 2018-11-12 17:30:40 +08:00
mcy
66912d3d05 rdb etl count destination 接口适配
es hbase 多源适配
2018-11-12 15:07:46 +08:00
agapple
ec1ac6d11c Merge pull request #1131 from rewerma/master
rdb同步适配多数据源
2018-11-12 10:25:37 +08:00
machey
7cca8857ff README.md 修改 2018-11-11 19:56:14 +08:00
machey
e7086415f8 rdb专用的的ClassExtensionLoader,默认canal client logger example配置 2018-11-11 19:44:30 +08:00
machey
4a27d81da1 Merge branch 'master' of https://github.com/rewerma/canal 2018-11-11 18:34:46 +08:00
rewerma
739425e99c Merge pull request #19 from alibaba/master
merge
2018-11-11 18:33:46 +08:00
agapple
cc1574d913 fixed adapter 2018-11-11 01:15:02 +08:00
machey
d7ab6e2dc1 rdb同步适配多数据源 2018-11-10 23:55:09 +08:00
agapple
808bedf984 Merge pull request #1128 from spccold/master
fixed DatabaseTableMeta#compareTableMeta
2018-11-10 12:17:09 +08:00
agapple
357eb885b2 Merge pull request #1127 from rewerma/master
canal server端 MQ 配置修改
2018-11-10 12:16:31 +08:00
wuwo
417e04f655 fixed DatabaseTableMeta#compareTableMeta 2018-11-09 19:23:03 +08:00
mcy
2d41bb9960 修改mq配置针对destination级别重启 2018-11-09 18:19:51 +08:00
mcy
7d7c82ce91 修改mq配置针对destination级别重启 2018-11-09 18:12:04 +08:00
mcy
0c3f7a806e 代码整理 2018-11-09 17:06:42 +08:00
mcy
2a38bca34b 代码整理 2018-11-09 16:52:34 +08:00
mcy
c5bd42bbf1 canal server端 MQ 配置修改
MQ配置可动态加载
2018-11-09 16:39:52 +08:00
agapple
8c1f15a49e Merge pull request #1126 from rewerma/master
关系型数据同步adapter, 支持 mysql oracle postgresql sqlserver
2018-11-09 15:26:07 +08:00
agapple
f9778a0aff fixed issue #1087, support kafka docker check 2018-11-09 13:58:39 +08:00
mcy
2ac84b4320 关系型数据同步adapter, 支持 mysql oracle postgresql sqlserver 2018-11-08 19:34:15 +08:00
jiangahng.loujh
0677443ccf fixed issue #1100 , fix type compare 2018-11-08 13:57:12 +08:00
jiangahng.loujh
2c1f3e649a fixed issue #1107 , more info message 2018-11-08 13:50:23 +08:00
agapple
4faf5428b1 Merge pull request #1120 from rewerma/master
flagMessage json 序列化无null值的bug
2018-11-08 13:39:09 +08:00
mcy
b71472661b Merge branch 'master' into feature/mcy 2018-11-08 11:23:47 +08:00
mcy
6fa111866f flagMessage json 序列化无null值的bug 2018-11-08 11:22:34 +08:00
mcy
f7ea8df89e rdb新增同步测试完成 2018-11-08 11:19:50 +08:00
agapple
041225c3f2 Merge pull request #1118 from littleneko/patch-1
fix: buildLastTransactionPosition 的 for 循环少了 =0
2018-11-07 21:58:30 +08:00
agapple
a8d708ea1b Merge pull request #1116 from spccold/master
a little improvement for DatabaseTableMeta
2018-11-07 21:55:54 +08:00
mcy
5ae7112192 init rdb sync 2018-11-07 21:01:50 +08:00
littleneko
2278340c7b fix: buildLastTransactionPosition 的 for 循环少了 =0 2018-11-07 20:24:38 +08:00
wuwo
87abaee8a7 a little improvement for DatabaseTableMeta 2018-11-07 15:50:33 +08:00
agapple
8f2e2b9ccd Merge pull request #1108 from spccold/master
skip tsdb snapshot apply if has not receive new ddl change from last …
2018-11-07 15:01:43 +08:00
agapple
d2ecb0aad9 Merge pull request #1112 from rewerma/master
client 排除jsr305不打进fat jar
2018-11-07 15:00:40 +08:00
mcy
1126d26574 Merge branch 'master' into feature/mcy 2018-11-07 11:40:06 +08:00
mcy
b71e299161 kafka单个partition的发送未改为同步发送的bug fix 2018-11-07 11:37:16 +08:00
mcy
572ea9f27a Merge branch 'master' into feature/mcy 2018-11-07 09:34:00 +08:00
mcy
bc298bedd5 支持hbase rowkey指定数字长度 2018-11-07 09:32:52 +08:00
mcy
202557630c 调整etl锁的粒度 2018-11-07 09:05:47 +08:00
machey
c27d5fa162 ETL整个destination,同步开关无效的问题 2018-11-07 02:04:08 +08:00
machey
3090557eeb client 排除jsr305不打进fat jar 2018-11-06 23:16:11 +08:00
machey
6de9f7efa7 修改 adapter release 打包名称 2018-11-06 22:49:34 +08:00
wuwo
f2b8d000f7 skip tsdb snapshot apply if has not receive new ddl change from last snapshot time 2018-11-06 16:59:44 +08:00
agapple
8ee546d34d Merge pull request #1106 from rewerma/master
修改adapter打包,启动方式
2018-11-06 16:24:31 +08:00
mcy
5b94c69d10 修改adapter打包,启动方式 2018-11-06 14:52:17 +08:00
mcy
8ae192f0a9 adapter canal-client 版本 2018-11-06 09:56:16 +08:00
rewerma
2e1a1c31e6 Merge pull request #18 from alibaba/master
merge
2018-11-06 09:50:11 +08:00
agapple
238da873b2 Merge pull request #1104 from jiacheo/master
修复rocketmq&kafka 发送消息失败时回滚后继续提交的问题
2018-11-05 23:08:34 +08:00
jiacheo
edb5f294ef fix:rollback之后继续commit,会产生 CanalServerException: ack error , clientId:1001 batchId:12 is not exist , please check 的错误。 2018-11-05 22:00:56 +08:00
agapple
5a76e2aa58 Merge pull request #1103 from wingerx/master
增加alter rename 的ddl 类型为rename
2018-11-05 19:50:20 +08:00
winger
1e20cac0da Merge remote-tracking branch 'upstream/master' 2018-11-05 18:39:47 +08:00
winger
d8e411462e 增加alter rename 的类型为rename 2018-11-05 18:38:44 +08:00
agapple
59e65ed4a6 Merge pull request #1099 from spccold/master
destory TableMetaTSDB when stop EventParser
2018-11-05 17:39:50 +08:00
agapple
4319b5e888 Merge pull request #1098 from rewerma/master
ES Adapter 支持
2018-11-05 17:32:26 +08:00
mcy
0d9695c3a5 增加对array和object类型的支持 2018-11-05 17:29:11 +08:00
七锋
359aa1a074 fixed compiler failed 2018-11-05 16:53:15 +08:00
wuwo
e608f09b53 improve DatabaseTableMeta 2018-11-05 16:37:42 +08:00
wuwo
beed10901d destory TableMetaTSDB when stop EventParser 2018-11-05 16:22:15 +08:00
mcy
dab6fa5aa1 Merge branch 'master' of https://github.com/rewerma/canal 2018-11-05 15:31:41 +08:00
rewerma
30d20f824b Merge pull request #17 from alibaba/master
merge
2018-11-05 15:31:02 +08:00
mcy
80cf533b05 整理注释 2018-11-05 15:24:32 +08:00
七锋
6ff57f53f8 fixed issue #1070, support mysql8.0.13 2018-11-05 15:06:40 +08:00
mcy
650446de5b etl实现 2018-11-05 13:36:35 +08:00
mcy
347601b9fe 同步测试类 2018-11-05 11:40:27 +08:00
mcy
5c8a5998d9 修改外键 2018-11-05 10:06:45 +08:00
mcy
391e040c6a 删除测试完成
后续: 修改 删除外键的情况, 修改主键的情况适配
2018-11-02 17:19:52 +08:00
七锋
8597d9e926 fixed more info 2018-11-02 12:51:36 +08:00
agapple
a895ce666e Merge pull request #1090 from jiacheo/master
CanalMessageDeserializer.deserializer 方法和FlatMessage.messageConverter 配合的问题
2018-11-02 11:10:22 +08:00
mcy
9d113dbc13 Merge branch 'master' into feature/mcy
# Conflicts:
#	client-adapter/launcher/src/main/resources/application-sample.yml
#	client-adapter/launcher/src/main/resources/application.yml
2018-11-02 11:08:57 +08:00
mcy
5cc2f8f9f7 增加file的path读取 2018-11-02 11:07:41 +08:00
mcy
5b9ada1f8c 修改测试完成 2018-11-02 09:51:30 +08:00
jiacheo
369059fa39 Merge pull request #1 from jiacheo/fix-lazyParseEntry-raw-wrong-value
Update CanalMessageDeserializer.java
2018-11-02 01:53:08 +08:00
jiacheo
9473f4e8fd Update CanalMessageDeserializer.java
fix lazyParseEntry 'raw' wrong value.
2018-11-02 01:48:17 +08:00
rewerma
685053599f Merge pull request #1086 from rewerma/master
release包不打进application.yml
2018-11-01 23:44:41 +08:00
machey
a9727cf267 release包不打进application.yml 2018-11-01 23:43:53 +08:00
agapple
bc399ba9f9 Merge pull request #1069 from spccold/master
pass useDruidDdlFilter parameter to LogEventConvert
2018-11-01 22:58:01 +08:00
mcy
81f403a9f2 更新完成 2018-11-01 21:07:45 +08:00
wuwo
332f759f96 filter dml before apply to tsdb 2018-11-01 17:38:18 +08:00
wuwo
5d5aafb7cd Merge remote-tracking branch 'upstream/master' 2018-11-01 17:28:29 +08:00
mcy
f42f7cfe72 Merge branch 'master' into feature/mcy
# Conflicts:
#	client-adapter/launcher/src/main/resources/application.yml
2018-11-01 16:39:56 +08:00
mcy
96b9484f70 modify 2018-11-01 16:37:47 +08:00
七锋
3bb408194d fixed issue #1081 , support mariadb rotate_event support checksum 2018-11-01 16:03:40 +08:00
mcy
865ba94605 application.yml配置打包设置默认模板 2018-11-01 15:22:14 +08:00
mcy
bb883cd228 Merge branch 'master' into feature/mcy 2018-11-01 14:53:51 +08:00
rewerma
d3e1ddd234 Merge pull request #1084 from rewerma/master
fix #1083
2018-11-01 14:47:33 +08:00
mcy
b9c4884af1 fix #1083 2018-11-01 14:46:29 +08:00
mcy
a6b16eedfa modify 2018-11-01 14:43:59 +08:00
mcy
9d04a82c42 新增完成 2018-11-01 14:32:11 +08:00
mcy
c9ad18894d 从表插入, 主表为简单字段 2018-11-01 11:33:12 +08:00
mcy
b2b44555ca 从表插入, 主表为简单字段 2018-11-01 11:07:35 +08:00
mcy
14e094b522 modify 2018-10-31 18:47:16 +08:00
agapple
0dea4b7944 Merge pull request #1079 from rewerma/master
application.yml不打包进jar
2018-10-31 17:34:33 +08:00
mcy
94a3cfa9de Merge branch 'master' into feature/mcy 2018-10-31 17:18:57 +08:00
mcy
ed1feb9502 application.yml不打包进jar, 解决jar中的application.yml和config中的会合并bug 2018-10-31 16:58:50 +08:00
mcy
a4856b9a73 modify 2018-10-31 16:55:48 +08:00
七锋
ff3ccde62a fixed rds binlog dump 2018-10-31 14:25:54 +08:00
mcy
2a2977eca7 主表带函数新增 2018-10-31 14:00:06 +08:00
七锋
1d9e2ccedd fixed issue #1076 , support rds binlog expired 2018-10-31 12:51:02 +08:00
七锋
dd8243a56d fixed issue #1075, support message transactor filter 2018-10-30 23:48:05 +08:00
mcy
2c9acb655d 单表新增 2018-10-30 19:52:05 +08:00
mcy
e1191b665e es配置加载 2018-10-30 15:57:00 +08:00
mcy
e4e23c9ecd 增加adapter相关参数: retry timeout batchSize 2018-10-30 14:13:51 +08:00
wuwo
4ce2b94241 rollback tsdb change 2018-10-30 11:49:49 +08:00
mcy
11ac253c46 adapter pom整理 2018-10-30 10:12:19 +08:00
mcy
1d2e85672c elasticsearch适配器 2018-10-30 09:49:04 +08:00
mcy
82b8b56aa3 partition默认为空 2018-10-30 09:10:49 +08:00
mcy
4e18d7800a 拼写错误 2018-10-30 09:08:34 +08:00
rewerma
7efd067bd5 Merge pull request #16 from alibaba/master
merge
2018-10-29 20:14:36 +08:00
wuwo
4c7129115c a little improve about tsdb 2018-10-29 17:49:34 +08:00
wuwo
0f14ed926b pass useDruidDdlFilter parameter to LogEventConvert 2018-10-29 17:32:10 +08:00
七锋
be5f1546ca fixed issue #1063 , rocketmq bugfix 2018-10-29 16:56:29 +08:00
agapple
f3160bf6cb Merge pull request #1065 from f-zhao/zhaofeng-branch
fix: fix NPE, 使用CanalServerWithManager时,开启GTID模式下,不传入masterPosition的情…
2018-10-29 16:42:46 +08:00
赵峰
fa79db65b7 fix: fix NPE, 使用CanalServerWithManager时,开启GTID模式下,不传入masterPosition的情况下,会产生NPE 2018-10-29 16:18:12 +08:00
七锋
d134aa0c20 fixed adjust default value 2018-10-29 15:59:32 +08:00
七锋
b9f40f326f [maven-release-plugin] prepare for next development iteration 2018-10-29 14:57:39 +08:00
321 changed files with 21609 additions and 5656 deletions

13
.github/issue_template.md vendored Normal file
View File

@@ -0,0 +1,13 @@
### environment
* canal version
* mysql version
### Issue Description
### Steps to reproduce
### Expected behaviour
### Actual behaviour

1
.gitignore vendored
View File

@@ -16,3 +16,4 @@ jtester.properties
.DS_Store
*.tar.gz
*.rpm
client-adapter/example/

BIN
.mvn/wrapper/maven-wrapper.jar vendored Executable file

Binary file not shown.

1
.mvn/wrapper/maven-wrapper.properties vendored Executable file
View File

@@ -0,0 +1 @@
distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip

21
.travis.yml Normal file
View File

@@ -0,0 +1,21 @@
language: java
sudo: false # faster builds
jdk:
- openjdk8
- oraclejdk8
cache:
directories:
- $HOME/.m2
install: true
script:
- travis_wait 30 ./mvnw clean install -DskipTests=true -Dcheckstyle.skip=true -Drat.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

View File

@@ -1,6 +1,12 @@
<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)
![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")
<h1>背景</h1>
<p style="font-size: 14px;"> 早期,阿里巴巴B2B公司因为存在杭州和美国双机房部署,存在跨机房同步的业务需求。不过早期的数据库同步业务,主要是基于trigger的方式获取增量变更,不过从2010年开始,阿里系公司开始逐步的尝试基于数据库的日志解析,获取增量变更进行同步,由此衍生出了增量订阅&amp;消费的业务,从此开启了一段新纪元。</p>
<p style="font-size: 14px;"> ps. 目前内部版本已经支持mysql和oracle部分版本的日志解析,当前的canal开源版本支持5.7及以下的版本(阿里内部mysql 5.7.13, 5.6.10, mysql 5.5.18和5.1.40/48)</p>
@@ -75,14 +81,14 @@ See the wiki page for : <a href="https://github.com/alibaba/canal/wiki" >wiki文
<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="/alibaba/canal/wiki/FAQ">FAQ</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/CanalClient/CanalSharp"> https://github.com/CanalSharp/CanalSharp </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)
@@ -117,7 +123,7 @@ See the wiki page for : <a href="https://github.com/alibaba/canal/wiki" >wiki文
<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/CanalClient/CanalSharp"> https://github.com/CanalClient/CanalSharp </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>

View File

@@ -19,14 +19,20 @@ client-adapter分为适配器和启动器两部分, 适配器为多个fat jar,
启动器为 SpringBoot 项目, 支持canal-client启动的同时提供相关REST管理接口, 运行目录结构为:
```
canal-adapter-launcher.jar
- 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
- config
...
- conf
application.yml
- hbase
mytest_person2.yml
- logs
```
以上目录结构最终会打包成 canal-adapter-launcher.tar.gz 压缩包
@@ -49,7 +55,7 @@ canal.conf:
hbase.zookeeper.quorum: slave1
hbase.zookeeper.property.clientPort: 2181
zookeeper.znode.parent: /hbase
mqTopics: # MQ topic租, 如果是kafka或者rockeMQ模式可配置此项, 与canalInstances不能并存
mqTopics: # MQ topic组, 如果是kafka或者rockeMQ模式可配置此项, 与canalInstances不能并存
- mqMode: kafka # MQ的模式: kafak/rocketMQ
topic: example # MQ topic
groups: # group组
@@ -96,7 +102,7 @@ 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 修改config/application.yml为:
#### 2.3.2 修改conf/application.yml为:
```
server:
port: 8081
@@ -118,9 +124,9 @@ canal.conf:
- outAdapters:
- name: logger
```
启动 canal-adapter-launcher.jar
启动
```
java -jar canal-adapter-launcher.jar
bin/startup.sh
```
## 三、HBase适配器
@@ -140,6 +146,11 @@ spring:
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:
@@ -149,19 +160,10 @@ canal.conf:
hbase.zookeeper.quorum: slave1
hbase.zookeeper.property.clientPort: 2181
zookeeper.znode.parent: /hbase
adapter.conf:
datasourceConfigs:
defaultDS:
url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true
username: root
password: 121212
adapterConfigs:
- hbase/mytest_person.yml
```
其中指定了一个HBase表映射文件: mytest_person.yml
adapter将会自动加载 conf/hbase 下的所有.yml结尾的配置文件
### 3.2 适配器表映射文件
修改 config/hbase/mytest_person.yml文件:
修改 conf/hbase/mytest_person.yml文件:
```
dataSourceKey: defaultDS # 对应application.yml中的datasourceConfigs下的配置
hbaseMapping: # mysql--HBase的单表映射配置
@@ -245,7 +247,299 @@ create 'MYTEST.PERSON', {NAME=>'CF'}
```
#### 启动canal-adapter启动器
```
java -jar canal-adapter-launcher.jar
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数据

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.1</version>
<version>1.1.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.alibaba.otter</groupId>
@@ -14,7 +14,7 @@
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal.protocol</artifactId>
<version>1.1.1</version>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
@@ -27,9 +27,14 @@
<version>1.1.9</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.40</version>
<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>

View File

@@ -2,6 +2,7 @@ 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;
@@ -21,15 +22,16 @@ public interface OuterAdapter {
* 外部适配器初始化接口
*
* @param configuration 外部适配器配置信息
* @param envProperties 环境变量的配置属性
*/
void init(OuterAdapterConfig configuration);
void init(OuterAdapterConfig configuration, Properties envProperties);
/**
* 往适配器中同步数据
*
* @param dml 数据包
* @param dmls 数据包
*/
void sync(Dml dml);
void sync(List<Dml> dmls);
/**
* 外部适配器销毁接口
@@ -38,7 +40,7 @@ public interface OuterAdapter {
/**
* Etl操作
*
*
* @param task 任务名, 对应配置名
* @param params etl筛选条件
*/
@@ -48,7 +50,7 @@ public interface OuterAdapter {
/**
* 计算总数
*
*
* @param task 任务名, 对应配置名
* @return 总数
*/
@@ -58,7 +60,7 @@ public interface OuterAdapter {
/**
* 通过task获取对应的destination
*
*
* @param task 任务名, 对应配置名
* @return destination
*/

View File

@@ -0,0 +1,155 @@
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);
}
}
}

View File

@@ -0,0 +1,97 @@
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;
}
}

View File

@@ -0,0 +1,31 @@
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);
}
}
}

View File

@@ -0,0 +1,52 @@
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();
}
}

View File

@@ -0,0 +1,27 @@
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);
}
}

View File

@@ -0,0 +1,356 @@
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) {
}
}

View File

@@ -0,0 +1,38 @@
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);
}

View File

@@ -0,0 +1,30 @@
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;
}
}

View File

@@ -0,0 +1,164 @@
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);
}
}
}

View File

@@ -0,0 +1,233 @@
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();
}
}

View File

@@ -0,0 +1,127 @@
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);
}
}
}
}

View File

@@ -0,0 +1,729 @@
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;
}
}
}

View File

@@ -0,0 +1,241 @@
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());
}
}

View File

@@ -0,0 +1,17 @@
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();
}
}

View File

@@ -0,0 +1,203 @@
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();
}
}

View File

@@ -0,0 +1,117 @@
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);
}
}

View File

@@ -0,0 +1,107 @@
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);
}
}

View File

@@ -0,0 +1,58 @@
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();
}

View File

@@ -0,0 +1,38 @@
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());
}
}

View File

@@ -0,0 +1,221 @@
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;
}
}

View File

@@ -0,0 +1,34 @@
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);
}
}

View File

@@ -0,0 +1,239 @@
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);
}
}
}

View File

@@ -0,0 +1,35 @@
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;
}

View File

@@ -0,0 +1,25 @@
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);
}

View File

@@ -0,0 +1,57 @@
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();
}

View File

@@ -0,0 +1,182 @@
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;
}
}
}

View File

@@ -0,0 +1,420 @@
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();
}
};
}
}
}

View File

@@ -0,0 +1,87 @@
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;
}
}
}

View File

@@ -1,41 +0,0 @@
package com.alibaba.otter.canal.client.adapter.support;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
/**
* 适配器配置集合, 用于配置加载, 线程不安全
*
* @author rewerma @ 2018-10-20
* @version 1.0.0
*/
public class AdapterConfigs {
/**
* 类型下对应所有配置名, 如:
* hbase
* ┗━ mytest_person.yml
* ┗━ mytest_role.yml
* ┗━ mytest_department.yml
*/
private static Map<String, Set<String>> configs = new HashMap<>();
public static void put(String key, String value) {
Set<String> values = configs.get(key);
if (values == null) {
values = new LinkedHashSet<>();
}
values.add(value);
configs.put(key, values);
}
public static Set<String> get(String key) {
return configs.get(key);
}
public static void clear() {
configs.clear();
}
}

View File

@@ -1,7 +1,8 @@
package com.alibaba.otter.canal.client.adapter.support;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 配置信息类
@@ -11,17 +12,30 @@ import java.util.List;
*/
public class CanalClientConfig {
private String canalServerHost; // 单机模式下canal server的 ip:port
// 单机模式下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 String zookeeperHosts; // 集群模式下的zk地址, 如果配置了单机地址则以单机为准!!
private String bootstrapServers; // kafka or rocket mq 地址
private Boolean flatMessage = true; // 是否已flatMessage模式传输, 只适用于mq模式
private List<MQTopic> mqTopics; // mq topic 列表
private List<CanalInstance> canalInstances; // tcp 模式下 canal 实例列表, 与mq模式不能共存!!
// canal adapters 配置
private List<CanalAdapter> canalAdapters;
public String getCanalServerHost() {
return canalServerHost;
@@ -39,20 +53,12 @@ public class CanalClientConfig {
this.zookeeperHosts = zookeeperHosts;
}
public String getBootstrapServers() {
return bootstrapServers;
public String getMqServers() {
return mqServers;
}
public void setBootstrapServers(String bootstrapServers) {
this.bootstrapServers = bootstrapServers;
}
public List<MQTopic> getMqTopics() {
return mqTopics;
}
public void setMqTopics(List<MQTopic> mqTopics) {
this.mqTopics = mqTopics;
public void setMqServers(String mqServers) {
this.mqServers = mqServers;
}
public Boolean getFlatMessage() {
@@ -63,17 +69,73 @@ public class CanalClientConfig {
this.flatMessage = flatMessage;
}
public List<CanalInstance> getCanalInstances() {
return canalInstances;
public Integer getBatchSize() {
return batchSize;
}
public void setCanalInstances(List<CanalInstance> canalInstances) {
this.canalInstances = canalInstances;
public void setBatchSize(Integer batchSize) {
this.batchSize = batchSize;
}
public static class CanalInstance {
public Integer getRetries() {
return retries;
}
private String instance; // 实例名
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 static class CanalAdapter {
private String instance; // 实例名
private List<Group> groups; // 适配器分组列表
@@ -94,60 +156,14 @@ public class CanalClientConfig {
public void setGroups(List<Group> groups) {
this.groups = groups;
}
}
public static class Group {
private List<OuterAdapterConfig> outAdapters; // 适配器列表
public List<OuterAdapterConfig> getOutAdapters() {
return outAdapters;
}
public void setOutAdapters(List<OuterAdapterConfig> outAdapters) {
this.outAdapters = outAdapters;
}
}
public static class MQTopic {
private String mqMode; // mq模式 kafka or rocketMQ
private String topic; // topic名
private List<MQGroup> groups = new ArrayList<>(); // 分组列表
public String getMqMode() {
return mqMode;
}
public void setMqMode(String mqMode) {
this.mqMode = mqMode;
}
public String getTopic() {
return topic;
}
public void setTopic(String topic) {
this.topic = topic;
}
public List<MQGroup> getGroups() {
return groups;
}
public void setGroups(List<MQGroup> groups) {
this.groups = groups;
}
}
public static class MQGroup {
private String groupId; // group id
private List<OuterAdapterConfig> outAdapters; // 适配器配置列表
// group id
private String groupId = "default";
private List<OuterAdapterConfig> outerAdapters; // 适配器列表
private Map<String, OuterAdapterConfig> outerAdaptersMap = new LinkedHashMap<>();
public String getGroupId() {
return groupId;
@@ -157,14 +173,20 @@ public class CanalClientConfig {
this.groupId = groupId;
}
public List<OuterAdapterConfig> getOutAdapters() {
return outAdapters;
public List<OuterAdapterConfig> getOuterAdapters() {
return outerAdapters;
}
public void setOutAdapters(List<OuterAdapterConfig> outAdapters) {
this.outAdapters = outAdapters;
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;
}
}
}

View File

@@ -0,0 +1,6 @@
package com.alibaba.otter.canal.client.adapter.support;
public class Constant {
public static final String CONF_DIR = "conf";
}

View File

@@ -15,8 +15,11 @@ 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; // 执行耗时
@@ -34,6 +37,14 @@ public class Dml implements Serializable {
this.destination = destination;
}
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public String getDatabase() {
return database;
}
@@ -50,6 +61,22 @@ public class Dml implements Serializable {
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;
}

View File

@@ -1,15 +1,22 @@
package com.alibaba.otter.canal.client.adapter.support;
import java.io.*;
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.*;
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;
@@ -37,6 +44,8 @@ public class ExtensionLoader<T> {
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;
@@ -83,47 +92,6 @@ public class ExtensionLoader<T> {
this.classLoaderPolicy = classLoaderPolicy;
}
public String getExtensionName(T extensionInstance) {
return getExtensionName(extensionInstance.getClass());
}
public String getExtensionName(Class<?> extensionClass) {
return cachedNames.get(extensionClass);
}
public ConcurrentHashMap<String, IllegalStateException> getExceptions() {
return exceptions;
}
/**
* 返回扩展点实例,如果没有指定的扩展点或是还没加载(即实例化)则返回<code>null</code>注意:此方法不会触发扩展点的加载
* <p/>
* 一般应该调用{@link #getExtension(String)}方法获得扩展,这个方法会触发扩展点加载
*
* @see #getExtension(String)
*/
@SuppressWarnings("unchecked")
public T getLoadedExtension(String name) {
if (name == null || name.length() == 0) throw new IllegalArgumentException("Extension name == null");
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<>());
holder = cachedInstances.get(name);
}
return (T) holder.get();
}
/**
* 返回已经加载的扩展点的名字
* <p/>
* 一般应该调用{@link #getSupportedExtensions()}方法获得扩展,这个方法会返回所有的扩展点
*
* @see #getSupportedExtensions()
*/
public Set<String> getLoadedExtensions() {
return Collections.unmodifiableSet(new TreeSet<>(cachedInstances.keySet()));
}
/**
* 返回指定名字的扩展
*
@@ -154,6 +122,31 @@ public class ExtensionLoader<T> {
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>
*/
@@ -165,87 +158,6 @@ public class ExtensionLoader<T> {
return getExtension(cachedDefaultName);
}
public boolean hasExtension(String name) {
if (name == null || name.length() == 0) throw new IllegalArgumentException("Extension name == null");
try {
return getExtensionClass(name) != null;
} catch (Throwable t) {
return false;
}
}
public Set<String> getSupportedExtensions() {
Map<String, Class<?>> clazzes = getExtensionClasses();
return Collections.unmodifiableSet(new TreeSet<String>(clazzes.keySet()));
}
/**
* 返回缺省的扩展点名,如果没有设置缺省则返回<code>null</code>
*/
public String getDefaultExtensionName() {
getExtensionClasses();
return cachedDefaultName;
}
/**
* 编程方式添加新扩展点
*
* @param name 扩展点名
* @param clazz 扩展点类
* @throws IllegalStateException 要添加扩展点名已经存在
*/
public void addExtension(String name, Class<?> clazz) {
getExtensionClasses(); // load classes
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Input type " + clazz + "not implement Extension " + type);
}
if (clazz.isInterface()) {
throw new IllegalStateException("Input type " + clazz + "can not be interface!");
}
if (name == null || "".equals(name)) {
throw new IllegalStateException("Extension name is blank (Extension " + type + ")!");
}
if (cachedClasses.get().containsKey(name)) {
throw new IllegalStateException("Extension name " + name + " already existed(Extension " + type + ")!");
}
cachedNames.put(clazz, name);
cachedClasses.get().put(name, clazz);
}
/**
* 编程方式添加替换已有扩展点
*
* @param name 扩展点名
* @param clazz 扩展点类
* @throws IllegalStateException 要添加扩展点名已经存在
* @deprecated 不推荐应用使用,一般只在测试时可以使用
*/
@Deprecated
public void replaceExtension(String name, Class<?> clazz) {
getExtensionClasses(); // load classes
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Input type " + clazz + "not implement Extension " + type);
}
if (clazz.isInterface()) {
throw new IllegalStateException("Input type " + clazz + "can not be interface!");
}
if (name == null || "".equals(name)) {
throw new IllegalStateException("Extension name is blank (Extension " + type + ")!");
}
if (!cachedClasses.get().containsKey(name)) {
throw new IllegalStateException("Extension name " + name + " not existed(Extension " + type + ")!");
}
cachedNames.put(clazz, name);
cachedClasses.get().put(name, clazz);
cachedInstances.remove(name);
}
@SuppressWarnings("unchecked")
private T createExtension(String name) {
Class<?> clazz = getExtensionClasses().get(name);
@@ -267,13 +179,25 @@ public class ExtensionLoader<T> {
}
}
private Class<?> getExtensionClass(String name) {
if (type == null) throw new IllegalArgumentException("Extension type == null");
if (name == null) throw new IllegalArgumentException("Extension name == null");
@SuppressWarnings("unchecked")
private T createExtension(String name, String key) {
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null)
throw new IllegalStateException("No such extension \"" + name + "\" for " + type.getName() + "!");
return clazz;
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() {
@@ -287,15 +211,19 @@ public class ExtensionLoader<T> {
}
}
}
return classes;
}
private String getJarDirectoryPath() {
URL url = Thread.currentThread().getContextClassLoader().getResource("");
if (url == null) {
throw new IllegalStateException("failed to get class loader resource");
String dirtyPath;
if (url != null) {
dirtyPath = url.toString();
} else {
File file = new File("");
dirtyPath = file.getAbsolutePath();
}
String dirtyPath = url.toString();
String jarPath = dirtyPath.replaceAll("^.*file:/", ""); // removes
// file:/ and
// everything
@@ -310,7 +238,11 @@ public class ExtensionLoader<T> {
// button.
jarPath = jarPath.replaceAll("/classes/.*", "/classes/");
}
return Paths.get(jarPath).getParent().toString(); // Paths - from java 8
Path path = Paths.get(jarPath).getParent(); // Paths - from java 8
if (path != null) {
return path.toString();
}
return null;
}
private Map<String, Class<?>> loadExtensionClasses() {
@@ -329,25 +261,20 @@ public class ExtensionLoader<T> {
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
// 1. lib folder,customized extension classLoader (jar_dir/lib)
String dir = File.separator + this.getJarDirectoryPath() + File.separator + "lib";
logger.info("extension classpath dir: " + dir);
// 1. plugin folder,customized 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_client" + File.separator + "lib");
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(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".jar");
}
});
File[] files = externalLibDir.listFiles((dir1, name) -> name.endsWith(".jar"));
if (files != null) {
for (File f : files) {
URL url = null;
URL url;
try {
url = f.toURI().toURL();
} catch (MalformedURLException e) {
@@ -358,49 +285,7 @@ public class ExtensionLoader<T> {
URLClassLoader localClassLoader;
if (classLoaderPolicy == null || "".equals(classLoaderPolicy)
|| DEFAULT_CLASSLOADER_POLICY.equalsIgnoreCase(classLoaderPolicy)) {
localClassLoader = new URLClassLoader(new URL[] { url }, parent) {
@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);
}
};
localClassLoader = new URLClassExtensionLoader(new URL[] { url });
} else {
localClassLoader = new URLClassLoader(new URL[] { url }, parent);
}
@@ -410,48 +295,15 @@ public class ExtensionLoader<T> {
}
}
}
// 只加载外部spi, 不加载classpath
// 2. load inner extension class with default classLoader
ClassLoader classLoader = findClassLoader();
loadFile(extensionClasses, CANAL_DIRECTORY, classLoader);
loadFile(extensionClasses, SERVICES_DIRECTORY, classLoader);
// ClassLoader classLoader = findClassLoader();
// loadFile(extensionClasses, CANAL_DIRECTORY, classLoader);
// loadFile(extensionClasses, SERVICES_DIRECTORY, classLoader);
return extensionClasses;
}
public 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();
}
}
}
private void loadFile(Map<String, Class<?>> extensionClasses, String dir, ClassLoader classLoader) {
String fileName = dir + type.getName();
try {
@@ -549,6 +401,7 @@ public class ExtensionLoader<T> {
}
}
@SuppressWarnings("unused")
private static ClassLoader findClassLoader() {
return ExtensionLoader.class.getClassLoader();
}

View File

@@ -2,9 +2,13 @@ package com.alibaba.otter.canal.client.adapter.support;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.*;
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.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -67,8 +71,14 @@ public class JdbcTypeUtil {
}
}
public static Object typeConvert(String columnName, String value, int sqlType, String mysqlType) {
if (value == null || value.equals("")) {
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;
}
@@ -115,23 +125,33 @@ public class JdbcTypeUtil {
break;
case Types.DATE:
if (!value.startsWith("0000-00-00")) {
value = value.trim().replace(" ", "T");
DateTime dt = new DateTime(value);
res = new Date(dt.toDate().getTime());
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:
value = "T" + value;
DateTime dt = new DateTime(value);
res = new Time(dt.toDate().getTime());
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")) {
value = value.trim().replace(" ", "T");
dt = new DateTime(value);
res = new Timestamp(dt.toDate().getTime());
java.util.Date date = Util.parseDate(value);
if (date != null) {
res = new Timestamp(date.getTime());
} else {
res = null;
}
} else {
res = null;
}
@@ -139,10 +159,11 @@ public class JdbcTypeUtil {
case Types.CLOB:
default:
res = value;
break;
}
return res;
} catch (Exception e) {
logger.error("table: {} column: {}, failed convert type {} to {}", columnName, value, sqlType);
logger.error("table: {} column: {}, failed convert type {} to {}", tableName, columnName, value, sqlType);
return value;
}
}

View File

@@ -0,0 +1,71 @@
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;
}
}

View File

@@ -1,11 +1,6 @@
package com.alibaba.otter.canal.client.adapter.support;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import com.alibaba.otter.canal.protocol.CanalEntry;
import com.alibaba.otter.canal.protocol.FlatMessage;
@@ -19,12 +14,12 @@ import com.alibaba.otter.canal.protocol.Message;
*/
public class MessageUtil {
public static void parse4Dml(String destination, Message message, Consumer<Dml> consumer) {
public static List<Dml> parse4Dml(String destination, String groupId, Message message) {
if (message == null) {
return;
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) {
@@ -42,18 +37,24 @@ public class MessageUtil {
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) {
@@ -70,8 +71,13 @@ public class MessageUtil {
}
for (CanalEntry.Column column : columns) {
if (i == 0) {
if (column.getIsKey()) {
dml.getPkNames().add(column.getName());
}
}
row.put(column.getName(),
JdbcTypeUtil.typeConvert(column.getName(),
JdbcTypeUtil.typeConvert(dml.getTable(),column.getName(),
column.getValue(),
column.getSqlType(),
column.getMysqlType()));
@@ -89,7 +95,7 @@ public class MessageUtil {
for (CanalEntry.Column column : rowData.getBeforeColumnsList()) {
if (updateSet.contains(column.getName())) {
rowOld.put(column.getName(),
JdbcTypeUtil.typeConvert(column.getName(),
JdbcTypeUtil.typeConvert(dml.getTable(),column.getName(),
column.getValue(),
column.getSqlType(),
column.getMysqlType()));
@@ -100,6 +106,8 @@ public class MessageUtil {
old.add(rowOld);
}
}
i++;
}
if (!data.isEmpty()) {
dml.setData(data);
@@ -108,38 +116,53 @@ public class MessageUtil {
dml.setOld(old);
}
}
consumer.accept(dml);
}
return dmls;
}
public static Dml flatMessage2Dml(String destination, FlatMessage flatMessage) {
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");
}
// 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(data, flatMessage.getSqlType(), flatMessage.getMysqlType()));
dml.setData(changeRows(dml.getTable(), data, flatMessage.getSqlType(), flatMessage.getMysqlType()));
}
List<Map<String, String>> old = flatMessage.getOld();
if (old != null) {
dml.setOld(changeRows(old, flatMessage.getSqlType(), flatMessage.getMysqlType()));
dml.setOld(changeRows(dml.getTable(), old, flatMessage.getSqlType(), flatMessage.getMysqlType()));
}
return dml;
}
private static List<Map<String, Object>> changeRows(List<Map<String, String>> rows, Map<String, Integer> sqlTypes,
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) {
@@ -158,16 +181,11 @@ public class MessageUtil {
continue;
}
Object finalValue = JdbcTypeUtil.typeConvert(columnName, columnValue, sqlType, mysqlType);
Object finalValue = JdbcTypeUtil.typeConvert(table, columnName, columnValue, sqlType, mysqlType);
resultRow.put(columnName, finalValue);
}
result.add(resultRow);
}
return result;
}
public interface Consumer<T> {
void accept(T t);
}
}

View File

@@ -12,6 +12,8 @@ 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地址
@@ -26,6 +28,14 @@ public class OuterAdapterConfig {
this.name = name;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getHosts() {
return hosts;
}

View File

@@ -12,10 +12,10 @@ import java.util.Date;
public class Result implements Serializable {
private static final long serialVersionUID = -3276409502352405716L;
public Integer code = 20000;
public Object data;
public String message;
public Date sysTime;
private Integer code = 20000;
private Object data;
private String message;
private Date sysTime;
public static Result createSuccess(String message) {
Result result = new Result();

View File

@@ -0,0 +1,88 @@
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();
}
}
}
}

View File

@@ -0,0 +1,273 @@
package com.alibaba.otter.canal.client.adapter.support;
import java.io.File;
import java.net.URL;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.Date;
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);
}
}
/**
* 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;
}
}

View File

@@ -0,0 +1,95 @@
<?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.3</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>

View File

@@ -0,0 +1,260 @@
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 org.slf4j.MDC;
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.DatasourceConfig;
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;
/**
* 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;
// ds不为空说明传入的是datasourceKey
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;
}
}

View File

@@ -0,0 +1,235 @@
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;
/**
* ES 映射配置
*
* @author rewerma 2018-11-01
* @version 1.0.0
*/
public class ESSyncConfig {
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 static class ESMapping {
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;
}
}
}

View File

@@ -0,0 +1,45 @@
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;
}
}

View File

@@ -0,0 +1,437 @@
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;
}
}
}

View File

@@ -0,0 +1,222 @@
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.SQLBinaryOpExpr;
import com.alibaba.fastsql.sql.ast.expr.SQLCaseExpr;
import com.alibaba.fastsql.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.fastsql.sql.ast.expr.SQLMethodInvokeExpr;
import com.alibaba.fastsql.sql.ast.expr.SQLPropertyExpr;
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));
}
}
/**
* 解析表
*
* @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");
}
}
}

View File

@@ -0,0 +1,165 @@
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());
}
}
}
}
}

View File

@@ -0,0 +1,314 @@
package com.alibaba.otter.canal.client.adapter.es.service;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.druid.pool.DruidDataSource;
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.DatasourceConfig;
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
import com.alibaba.otter.canal.client.adapter.support.Util;
import com.google.common.base.Joiner;
/**
* ES ETL Service
*
* @author rewerma 2018-11-01
* @version 1.0.0
*/
public class ESEtlService {
private static Logger logger = LoggerFactory.getLogger(ESEtlService.class);
private TransportClient transportClient;
private ESTemplate esTemplate;
private ESSyncConfig config;
public ESEtlService(TransportClient transportClient, ESSyncConfig config){
this.transportClient = transportClient;
this.esTemplate = new ESTemplate(transportClient);
this.config = config;
}
public EtlResult importData(List<String> params) {
EtlResult etlResult = new EtlResult();
AtomicLong impCount = new AtomicLong();
List<String> errMsg = new ArrayList<>();
String esIndex = "";
if (config == null) {
logger.warn("esSycnCofnig is null, etl go end !");
etlResult.setErrorMessage("esSycnCofnig is null, etl go end !");
return etlResult;
}
ESMapping mapping = config.getEsMapping();
esIndex = mapping.get_index();
DruidDataSource dataSource = DatasourceConfig.DATA_SOURCES.get(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);
logger.info("etl from db: {}, to es index: {}", schema, esIndex);
long start = System.currentTimeMillis();
try {
String sql = mapping.getSql();
// 拼接条件
if (mapping.getEtlCondition() != null && params != null) {
String etlCondition = mapping.getEtlCondition();
int size = params.size();
for (int i = 0; i < size; i++) {
etlCondition = etlCondition.replace("{" + i + "}", params.get(i));
}
sql += " " + etlCondition;
}
if (logger.isDebugEnabled()) {
logger.debug("etl sql : {}", mapping.getSql());
}
// 获取总数
String countSql = "SELECT COUNT(1) FROM ( " + sql + ") _CNT ";
long cnt = (Long) Util.sqlRS(dataSource, countSql, 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 = 3; // 从配置读取默认为3
long perThreadCnt = cnt / threadCount;
ExecutorService executor = Util.newFixedThreadPool(threadCount, 5000L);
for (int i = 0; i < threadCount; i++) {
long offset = i * perThreadCnt;
Long size = null;
if (i != threadCount - 1) {
size = perThreadCnt;
}
String sqlFinal;
if (size != null) {
sqlFinal = sql + " LIMIT " + offset + "," + size;
} else {
sqlFinal = sql + " LIMIT " + offset + "," + cnt;
}
executor.execute(() -> executeSqlImport(dataSource, sqlFinal, mapping, impCount, errMsg));
}
executor.shutdown();
while (!executor.awaitTermination(3, TimeUnit.SECONDS)) {
// ignore
}
} else {
executeSqlImport(dataSource, sql, mapping, impCount, errMsg);
}
logger.info("数据全量导入完成,一共导入 {} 条数据, 耗时: {}", impCount.get(), System.currentTimeMillis() - start);
etlResult.setResultMessage("导入ES索引 " + esIndex + " 数据:" + impCount.get() + " 条");
} catch (Exception e) {
logger.error(e.getMessage(), e);
errMsg.add(esIndex + " etl failed! ==>" + e.getMessage());
}
if (errMsg.isEmpty()) {
etlResult.setSucceeded(true);
} else {
etlResult.setErrorMessage(Joiner.on("\n").join(errMsg));
}
return etlResult;
}
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());
}
}
}
private boolean executeSqlImport(DataSource ds, String sql, ESMapping mapping, AtomicLong impCount,
List<String> errMsg) {
try {
Util.sqlRS(ds, sql, 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;
}
}
}

View File

@@ -0,0 +1,845 @@
package com.alibaba.otter.canal.client.adapter.es.service;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
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 ot 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 ot 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 ot 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 ot 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();
StringBuilder sql = new StringBuilder(mapping.getSql() + " 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 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 ot 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 ot 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 ot 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();
}
}

View File

@@ -0,0 +1,306 @@
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 ");
}
}
}

View File

@@ -0,0 +1,524 @@
package com.alibaba.otter.canal.client.adapter.es.support;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
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;
}
/**
* 插入数据
*
* @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 ");
paramsTmp.forEach(
(fieldName, value) -> sql.append("_v.").append(fieldName).append("=").append(value).append(" AND "));
int len = sql.length();
sql.delete(len - 4, len);
Integer syncCount = (Integer) Util.sqlRS(ds, sql.toString(), 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());
}
}
}
}
}
/**
* 如果大于批量数则提交批次
*/
private void commitBulk() {
if (getBulk().numberOfActions() >= MAX_BATCH_SIZE) {
commit();
}
}
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);
}
}

View File

@@ -0,0 +1 @@
es=com.alibaba.otter.canal.client.adapter.es.ESAdapter

View File

@@ -0,0 +1,21 @@
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>='{0}'"
commitBatch: 3000

View File

@@ -0,0 +1,47 @@
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>='{0}'"
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"
# }
# }
# }
# }
# }
#}

View File

@@ -0,0 +1,16 @@
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>='{0}'"
commitBatch: 3000

View File

@@ -0,0 +1,39 @@
package com.alibaba.otter.canal.client.adapter.es.test;
import java.util.Map;
import org.junit.Assert;
import org.junit.Before;
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;
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());
}
}

View File

@@ -0,0 +1,118 @@
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.Test;
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();
}
}

View File

@@ -0,0 +1,48 @@
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()));
}
}

View File

@@ -0,0 +1,40 @@
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();
}
}
}

View File

@@ -0,0 +1,68 @@
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
}
}
}
}
}

View File

@@ -0,0 +1,134 @@
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.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;
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"));
}
}

View File

@@ -0,0 +1,134 @@
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.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;
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"));
}
}

View File

@@ -0,0 +1,99 @@
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.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;
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"));
}
}

View File

@@ -0,0 +1,195 @@
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.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;
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"));
}
}

View File

@@ -0,0 +1,100 @@
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.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;
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"));
}
}

View File

@@ -0,0 +1,126 @@
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.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;
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);
// }
}

View File

@@ -0,0 +1,39 @@
-- ----------------------------
-- 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;

View File

@@ -0,0 +1,21 @@
{
"_doc": {
"properties": {
"_name": {
"type": "text"
},
"_role_id": {
"type": "long"
},
"_role_name": {
"type": "text"
},
"_labels": {
"type": "text"
},
"_c_time": {
"type": "date"
}
}
}
}

View File

@@ -0,0 +1,8 @@
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

View File

@@ -0,0 +1,13 @@
<?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>

View File

@@ -0,0 +1,13 @@
<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>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.1</version>
<version>1.1.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.alibaba.otter</groupId>
@@ -17,12 +17,6 @@
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.19</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
@@ -62,6 +56,26 @@
</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>

View File

@@ -1,10 +1,8 @@
package com.alibaba.otter.canal.client.adapter.hbase;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import javax.sql.DataSource;
@@ -12,8 +10,10 @@ 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.*;
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;
@@ -21,6 +21,7 @@ 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;
@@ -35,55 +36,111 @@ import com.alibaba.otter.canal.client.adapter.support.*;
@SPI("hbase")
public class HbaseAdapter implements OuterAdapter {
private static Logger logger = LoggerFactory.getLogger(HbaseAdapter.class);
private static Logger logger = LoggerFactory.getLogger(HbaseAdapter.class);
private static volatile Map<String, MappingConfig> hbaseMapping = null; // 文件名对应配置
private static volatile Map<String, MappingConfig> mappingConfigCache = null; // 库名-表名对应配置
private Map<String, MappingConfig> hbaseMapping = new ConcurrentHashMap<>(); // 文件名对应配置
private Map<String, Map<String, MappingConfig>> mappingConfigCache = new ConcurrentHashMap<>(); // 库名-表名对应配置
private Connection conn;
private HbaseSyncService hbaseSyncService;
private HbaseTemplate hbaseTemplate;
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) {
public void init(OuterAdapterConfig configuration, Properties envProperties) {
try {
if (mappingConfigCache == null) {
synchronized (MappingConfig.class) {
if (mappingConfigCache == null) {
hbaseMapping = MappingConfigLoader.load();
mappingConfigCache = new HashMap<>();
for (MappingConfig mappingConfig : hbaseMapping.values()) {
mappingConfigCache.put(StringUtils.trimToEmpty(mappingConfig.getHbaseMapping().getDestination())
+ "." + mappingConfig.getHbaseMapping().getDatabase() + "."
+ mappingConfig.getHbaseMapping().getTable(),
mappingConfig);
}
}
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> propertites = configuration.getProperties();
Map<String, String> properties = configuration.getProperties();
Configuration hbaseConfig = HBaseConfiguration.create();
propertites.forEach(hbaseConfig::set);
conn = ConnectionFactory.createConnection(hbaseConfig);
hbaseTemplate = new HbaseTemplate(conn);
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(Dml dml) {
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();
MappingConfig config = mappingConfigCache.get(destination + "." + database + "." + table);
hbaseSyncService.sync(config, dml);
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
@@ -100,33 +157,34 @@ public class HbaseAdapter implements OuterAdapter {
return etlResult;
}
} else {
DataSource dataSource = DatasourceConfig.DATA_SOURCES.get(task);
if (dataSource != null) {
StringBuilder resultMsg = new StringBuilder();
boolean resSucc = true;
// ds不为空说明传入的是datasourceKey
for (MappingConfig configTmp : hbaseMapping.values()) {
// 取所有的datasourceKey为task的配置
if (configTmp.getDataSourceKey().equals(task)) {
EtlResult etlRes = HbaseEtlService.importData(dataSource, hbaseTemplate, configTmp, params);
if (!etlRes.getSucceeded()) {
resSucc = false;
resultMsg.append(etlRes.getErrorMessage()).append("\n");
} else {
resultMsg.append(etlRes.getResultMessage()).append("\n");
}
StringBuilder resultMsg = new StringBuilder();
boolean resSucc = true;
// ds不为空说明传入的是datasourceKey
for (MappingConfig configTmp : hbaseMapping.values()) {
// 取所有的destination为task的配置
if (configTmp.getDestination().equals(task)) {
DataSource dataSource = DatasourceConfig.DATA_SOURCES.get(configTmp.getDataSourceKey());
if (dataSource == null) {
continue;
}
}
if (resultMsg.length() > 0) {
etlResult.setSucceeded(resSucc);
if (resSucc) {
etlResult.setResultMessage(resultMsg.toString());
EtlResult etlRes = HbaseEtlService.importData(dataSource, hbaseTemplate, configTmp, params);
if (!etlRes.getSucceeded()) {
resSucc = false;
resultMsg.append(etlRes.getErrorMessage()).append("\n");
} else {
etlResult.setErrorMessage(resultMsg.toString());
resultMsg.append(etlRes.getResultMessage()).append("\n");
}
return etlResult;
}
}
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");
@@ -139,7 +197,7 @@ public class HbaseAdapter implements OuterAdapter {
String hbaseTable = config.getHbaseMapping().getHbaseTable();
long rowCount = 0L;
try {
HTable table = (HTable) conn.getTable(TableName.valueOf(hbaseTable));
HTable table = (HTable) hbaseTemplate.getConnection().getTable(TableName.valueOf(hbaseTable));
Scan scan = new Scan();
scan.setFilter(new FirstKeyOnlyFilter());
ResultScanner resultScanner = table.getScanner(scan);
@@ -157,12 +215,13 @@ public class HbaseAdapter implements OuterAdapter {
@Override
public void destroy() {
if (conn != null) {
try {
conn.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
if (configMonitor != null) {
configMonitor.destroy();
}
try {
hbaseTemplate.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@@ -170,7 +229,7 @@ public class HbaseAdapter implements OuterAdapter {
public String getDestination(String task) {
MappingConfig config = hbaseMapping.get(task);
if (config != null && config.getHbaseMapping() != null) {
return config.getHbaseMapping().getDestination();
return config.getDestination();
}
return null;
}

View File

@@ -10,9 +10,15 @@ import java.util.*;
*/
public class MappingConfig {
private String dataSourceKey; // 数据源key
private String dataSourceKey; // 数据源key
private HbaseMapping hbaseMapping; // hbase映射配置
private String outerAdapterKey; // adapter key
private String groupId; // groupId
private String destination; // canal实例或MQ的topic
private HbaseMapping hbaseMapping; // hbase映射配置
public String getDataSourceKey() {
return dataSourceKey;
@@ -22,6 +28,30 @@ public class MappingConfig {
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;
}
@@ -66,6 +96,7 @@ public class MappingConfig {
public static class ColumnItem {
private boolean isRowKey = false;
private Integer rowKeyLen;
private String column;
private String family;
private String qualifier;
@@ -79,6 +110,14 @@ public class MappingConfig {
isRowKey = rowKey;
}
public Integer getRowKeyLen() {
return rowKeyLen;
}
public void setRowKeyLen(Integer rowKeyLen) {
this.rowKeyLen = rowKeyLen;
}
public String getColumn() {
return column;
}
@@ -143,7 +182,6 @@ public class MappingConfig {
public static class HbaseMapping {
private Mode mode = Mode.STRING; // hbase默认转换格式
private String destination; // canal实例或MQ的topic
private String database; // 数据库名或schema名
private String table; // 表面名
private String hbaseTable; // hbase表名
@@ -169,14 +207,6 @@ public class MappingConfig {
this.mode = mode;
}
public String getDestination() {
return destination;
}
public void setDestination(String destination) {
this.destination = destination;
}
public String getDatabase() {
return database;
}
@@ -263,7 +293,16 @@ public class MappingConfig {
ColumnItem columnItem = new ColumnItem();
columnItem.setColumn(columnField.getKey());
columnItem.setType(type);
if ("rowKey".equalsIgnoreCase(field)) {
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 {

View File

@@ -1,19 +1,14 @@
package com.alibaba.otter.canal.client.adapter.hbase.config;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.Yaml;
import com.alibaba.otter.canal.client.adapter.support.AdapterConfigs;
import com.alibaba.otter.canal.client.adapter.config.YmlConfigBinder;
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
/**
* HBase表映射配置加载器
@@ -23,127 +18,34 @@ import com.alibaba.otter.canal.client.adapter.support.AdapterConfigs;
*/
public class MappingConfigLoader {
private static Logger logger = LoggerFactory.getLogger(MappingConfigLoader.class);
private static final String BASE_PATH = "hbase";
private static Logger logger = LoggerFactory.getLogger(MappingConfigLoader.class);
/**
* 加载HBase表映射配置
*
*
* @return 配置名/配置文件名--对象
*/
public static Map<String, MappingConfig> load() {
logger.info("## Start loading mapping config ... ");
public static Map<String, MappingConfig> load(Properties envProperties) {
logger.info("## Start loading hbase mapping config ... ");
Map<String, MappingConfig> result = new LinkedHashMap<>();
Collection<String> configs = AdapterConfigs.get("hbase");
for (String c : configs) {
if (c == null) {
continue;
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;
}
c = c.trim();
if (c.equals("") || c.startsWith("#")) {
continue;
}
MappingConfig config;
String configContent = null;
if (c.endsWith(".yml")) {
configContent = readConfigContent(BASE_PATH + "/" + c);
}
// 简单配置database.table@datasourcekey?rowKey=key1,key2
if (StringUtils.isEmpty(configContent)) {
String[] mapping = c.split("\\?");
String params = mapping.length == 2 ? mapping[1] : null;
String rowKey = null;
String srcMeta = mapping[0];
//
if (params != null) {
for (String entry : params.split("&")) {
if ("rowKey".equals(entry.split("=")[0])) {
rowKey = entry.split("=")[1];
}
}
}
String dsKey = srcMeta.split("@").length == 2 ? srcMeta.split("@")[1] : null;
String[] dbTable;
if (dsKey == null) {
dbTable = srcMeta.split("\\.");
} else {
dbTable = srcMeta.split("@")[0].split("\\.");
}
if (dbTable.length == 2) {
config = new MappingConfig();
MappingConfig.HbaseMapping hbaseMapping = new MappingConfig.HbaseMapping();
hbaseMapping.setHbaseTable(dbTable[0].toUpperCase() + "." + dbTable[1].toUpperCase());
hbaseMapping.setAutoCreateTable(true);
hbaseMapping.setDatabase(dbTable[0]);
hbaseMapping.setTable(dbTable[1]);
hbaseMapping.setMode(MappingConfig.Mode.PHOENIX);
hbaseMapping.setRowKey(rowKey);
// 有定义rowKey
if (rowKey != null) {
MappingConfig.ColumnItem columnItem = new MappingConfig.ColumnItem();
columnItem.setRowKey(true);
columnItem.setColumn(rowKey);
hbaseMapping.setRowKeyColumn(columnItem);
}
config.setHbaseMapping(hbaseMapping);
config.setDataSourceKey(dsKey);
} else {
throw new RuntimeException(String.format("配置项[%s]内容为空, 或格式不符合database.table", c));
}
} else { // 配置文件配置
config = new Yaml().loadAs(configContent, MappingConfig.class);
}
try {
config.validate();
} catch (Exception e) {
throw new RuntimeException("ERROR Config: " + c + " " + e.getMessage(), e);
throw new RuntimeException("ERROR load Config: " + fileName + " " + e.getMessage(), e);
}
result.put(c, config);
}
result.put(fileName, config);
});
logger.info("## Mapping config loaded");
logger.info("## Hbase mapping config loaded");
return result;
}
public static String readConfigContent(String config) {
InputStream in = null;
try {
// 先取本地文件,再取类路径
File configFile = new File("config/" + config);
if (configFile.exists()) {
in = new FileInputStream(configFile);
} else {
in = MappingConfigLoader.class.getClassLoader().getResourceAsStream(config);
}
if (in == null) {
throw new RuntimeException("Config file not found.");
}
byte[] bytes = new byte[in.available()];
in.read(bytes);
return new String(bytes, "UTF-8");
} catch (IOException e) {
throw new RuntimeException("Read ds-config.yml or hbase-mappings.conf error. ", e);
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException e) {
// ignore
}
}
}
}

View File

@@ -0,0 +1,145 @@
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());
}
}
}
}
}

View File

@@ -1,16 +1,14 @@
package com.alibaba.otter.canal.client.adapter.hbase.service;
import java.sql.*;
import java.sql.ResultSetMetaData;
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.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;
import javax.sql.DataSource;
@@ -19,9 +17,15 @@ 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.alibaba.otter.canal.client.adapter.hbase.support.HRow;
import com.alibaba.otter.canal.client.adapter.hbase.support.HbaseTemplate;
import com.alibaba.otter.canal.client.adapter.hbase.support.PhType;
import com.alibaba.otter.canal.client.adapter.hbase.support.PhTypeUtil;
import com.alibaba.otter.canal.client.adapter.hbase.support.Type;
import com.alibaba.otter.canal.client.adapter.hbase.support.TypeUtil;
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
import com.alibaba.otter.canal.client.adapter.support.JdbcTypeUtil;
import com.alibaba.otter.canal.client.adapter.support.Util;
import com.google.common.base.Joiner;
/**
@@ -34,47 +38,9 @@ public class HbaseEtlService {
private static Logger logger = LoggerFactory.getLogger(HbaseEtlService.class);
public static Object sqlRS(DataSource ds, String sql, Function<ResultSet, Object> fun) throws SQLException {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
conn = ds.getConnection();
stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
stmt.setFetchSize(Integer.MIN_VALUE);
rs = stmt.executeQuery(sql);
return fun.apply(rs);
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
logger.error(e.getMessage(), e);
}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
logger.error(e.getMessage(), e);
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
logger.error(e.getMessage(), e);
}
}
rs = null;
stmt = null;
conn = null;
}
}
/**
* 建表
*
*
* @param hbaseTemplate
* @param config
*/
@@ -93,7 +59,7 @@ public class HbaseEtlService {
/**
* 导入数据
*
*
* @param ds 数据源
* @param hbaseTemplate hbaseTemplate
* @param config 配置
@@ -138,7 +104,7 @@ public class HbaseEtlService {
if (params != null && params.size() == 1 && hbaseMapping.getEtlCondition() == null) {
AtomicBoolean stExists = new AtomicBoolean(false);
// 验证是否有SYS_TIME字段
sqlRS(ds, sql, rs -> {
Util.sqlRS(ds, sql, rs -> {
try {
ResultSetMetaData rsmd = rs.getMetaData();
int cnt = rsmd.getColumnCount();
@@ -169,7 +135,7 @@ public class HbaseEtlService {
// 获取总数
String countSql = "SELECT COUNT(1) FROM ( " + sql + ") _CNT ";
long cnt = (Long) sqlRS(ds, countSql, rs -> {
long cnt = (Long) Util.sqlRS(ds, countSql, rs -> {
Long count = null;
try {
if (rs.next()) {
@@ -185,8 +151,7 @@ public class HbaseEtlService {
if (cnt >= 10000) {
int threadCount = 3;
long perThreadCnt = cnt / threadCount;
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
List<Future<Boolean>> futures = new ArrayList<>(threadCount);
ExecutorService executor = Util.newFixedThreadPool(threadCount, 5000L);
for (int i = 0; i < threadCount; i++) {
long offset = i * perThreadCnt;
Long size = null;
@@ -199,22 +164,20 @@ public class HbaseEtlService {
} else {
sqlFinal = sql + " LIMIT " + offset + "," + cnt;
}
Future<Boolean> future = executor
.submit(() -> executeSqlImport(ds, sqlFinal, hbaseMapping, hbaseTemplate, successCount, errMsg));
futures.add(future);
}
for (Future<Boolean> future : futures) {
future.get();
executor.submit(
() -> executeSqlImport(ds, sqlFinal, hbaseMapping, hbaseTemplate, successCount, errMsg));
}
executor.shutdown();
while (!executor.awaitTermination(3, TimeUnit.SECONDS)) {
// ignore
}
} else {
executeSqlImport(ds, sql, hbaseMapping, hbaseTemplate, successCount, errMsg);
}
logger.info(
hbaseMapping.getHbaseTable() + " etl completed in: " + (System.currentTimeMillis() - start) / 1000 + "s!");
logger.info(hbaseMapping.getHbaseTable() + " etl completed in: "
+ (System.currentTimeMillis() - start) / 1000 + "s!");
etlResult.setResultMessage("导入HBase表 " + hbaseMapping.getHbaseTable() + " 数据:" + successCount.get() + " 条");
} catch (Exception e) {
@@ -232,7 +195,7 @@ public class HbaseEtlService {
/**
* 执行导入
*
*
* @param ds
* @param sql
* @param hbaseMapping
@@ -244,7 +207,7 @@ public class HbaseEtlService {
private static boolean executeSqlImport(DataSource ds, String sql, MappingConfig.HbaseMapping hbaseMapping,
HbaseTemplate hbaseTemplate, AtomicLong successCount, List<String> errMsg) {
try {
sqlRS(ds, sql, rs -> {
Util.sqlRS(ds, sql, rs -> {
int i = 1;
try {
@@ -332,18 +295,49 @@ public class HbaseEtlService {
byte[] valBytes = Bytes.toBytes(val.toString());
if (columnItem.isRowKey()) {
row.setRowKey(valBytes);
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 {
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 (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));
}
}
}
}
@@ -382,4 +376,16 @@ public class HbaseEtlService {
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;
}
}

View File

@@ -6,6 +6,8 @@ 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;
@@ -18,38 +20,33 @@ import com.alibaba.otter.canal.client.adapter.support.Dml;
*/
public class HbaseSyncService {
private Logger logger = LoggerFactory.getLogger(this.getClass());
private static Logger logger = LoggerFactory.getLogger(HbaseSyncService.class);
private HbaseTemplate hbaseTemplate; // HBase操作模板
private HbaseTemplate hbaseTemplate; // HBase操作模板
public HbaseSyncService(HbaseTemplate hbaseTemplate){
this.hbaseTemplate = hbaseTemplate;
}
public void sync(MappingConfig config, Dml dml) {
try {
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()) {
String res = dml.toString();
logger.debug(res);
}
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));
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
/**
* 插入操作
*
*
* @param config 配置项
* @param dml DML数据
*/
@@ -101,7 +98,7 @@ public class HbaseSyncService {
/**
* 将Map数据转换为HRow行数据
*
*
* @param hbaseMapping hbase映射配置
* @param hRow 行对象
* @param data Map数据
@@ -132,7 +129,21 @@ public class HbaseSyncService {
}
} else {
if (columnItem.isRowKey()) {
// row.put("rowKey", bytes);
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);
@@ -145,9 +156,9 @@ public class HbaseSyncService {
/**
* 更新操作
*
* @param config
* @param dml
*
* @param config 配置对象
* @param dml dml对象
*/
private void update(MappingConfig config, Dml dml) {
List<Map<String, Object>> data = dml.getData();
@@ -192,14 +203,15 @@ public class HbaseSyncService {
Map<String, Object> rowKey = data.get(0);
rowKeyBytes = typeConvert(null, hbaseMapping, rowKey.values().iterator().next());
} else {
rowKeyBytes = typeConvert(rowKeyColumn, hbaseMapping, r.get(rowKeyColumn.getColumn()));
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)) {
if (hbaseMapping.getExcludeColumns() != null
&& hbaseMapping.getExcludeColumns().contains(updateColumn)) {
continue;
}
MappingConfig.ColumnItem columnItem = columnItems.get(updateColumn);
@@ -276,8 +288,7 @@ public class HbaseSyncService {
Map<String, Object> rowKey = data.get(0);
rowKeyBytes = typeConvert(null, hbaseMapping, rowKey.values().iterator().next());
} else {
Object val = r.get(rowKeyColumn.getColumn());
rowKeyBytes = typeConvert(rowKeyColumn, hbaseMapping, val);
rowKeyBytes = getRowKeyBytes(hbaseMapping, rowKeyColumn, r);
}
if (rowKeyBytes == null) throw new RuntimeException("rowKey值为空");
rowKeys.add(rowKeyBytes);
@@ -365,7 +376,7 @@ public class HbaseSyncService {
/**
* 根据对应的类型进行转换
*
*
* @param columnItem 列项配置
* @param hbaseMapping hbase映射配置
* @param value 值
@@ -423,4 +434,22 @@ public class HbaseSyncService {
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);
}
}
}

View File

@@ -5,10 +5,12 @@ 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;
@@ -25,16 +27,33 @@ import org.slf4j.LoggerFactory;
*/
public class HbaseTemplate {
private Logger logger = LoggerFactory.getLogger(this.getClass());
private Logger logger = LoggerFactory.getLogger(this.getClass());
private Connection conn;
private Configuration hbaseConfig; // hbase配置对象
private Connection conn; // hbase连接
public HbaseTemplate(Connection conn){
this.conn = conn;
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) conn.getAdmin()) {
try (HBaseAdmin admin = (HBaseAdmin) getConnection().getAdmin()) {
return admin.tableExists(TableName.valueOf(tableName));
} catch (IOException e) {
@@ -43,7 +62,7 @@ public class HbaseTemplate {
}
public void createTable(String tableName, String... familyNames) {
try (HBaseAdmin admin = (HBaseAdmin) conn.getAdmin()) {
try (HBaseAdmin admin = (HBaseAdmin) getConnection().getAdmin()) {
HTableDescriptor desc = new HTableDescriptor(TableName.valueOf(tableName));
// 添加列簇
@@ -60,7 +79,7 @@ public class HbaseTemplate {
}
public void disableTable(String tableName) {
try (HBaseAdmin admin = (HBaseAdmin) conn.getAdmin()) {
try (HBaseAdmin admin = (HBaseAdmin) getConnection().getAdmin()) {
admin.disableTable(tableName);
} catch (IOException e) {
logger.error(e.getMessage(), e);
@@ -69,7 +88,7 @@ public class HbaseTemplate {
}
public void deleteTable(String tableName) {
try (HBaseAdmin admin = (HBaseAdmin) conn.getAdmin()) {
try (HBaseAdmin admin = (HBaseAdmin) getConnection().getAdmin()) {
if (admin.isTableEnabled(tableName)) {
disableTable(tableName);
}
@@ -82,7 +101,7 @@ public class HbaseTemplate {
/**
* 插入一行数据
*
*
* @param tableName 表名
* @param hRow 行数据对象
* @return 是否成功
@@ -90,7 +109,7 @@ public class HbaseTemplate {
public Boolean put(String tableName, HRow hRow) {
boolean flag = false;
try {
HTable table = (HTable) conn.getTable(TableName.valueOf(tableName));
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());
@@ -99,6 +118,7 @@ public class HbaseTemplate {
flag = true;
} catch (Exception e) {
logger.error(e.getMessage(), e);
throw new RuntimeException(e);
}
return flag;
@@ -106,7 +126,7 @@ public class HbaseTemplate {
/**
* 批量插入
*
*
* @param tableName 表名
* @param rows 行数据对象集合
* @return 是否成功
@@ -114,7 +134,7 @@ public class HbaseTemplate {
public Boolean puts(String tableName, List<HRow> rows) {
boolean flag = false;
try {
HTable table = (HTable) conn.getTable(TableName.valueOf(tableName));
HTable table = (HTable) getConnection().getTable(TableName.valueOf(tableName));
List<Put> puts = new ArrayList<>();
for (HRow hRow : rows) {
Put put = new Put(hRow.getRowKey());
@@ -131,13 +151,14 @@ public class HbaseTemplate {
flag = true;
} catch (Exception e) {
logger.error(e.getMessage(), e);
throw new RuntimeException(e);
}
return flag;
}
/**
* 批量删除数据
*
*
* @param tableName 表名
* @param rowKeys rowKey集合
* @return 是否成功
@@ -145,7 +166,7 @@ public class HbaseTemplate {
public Boolean deletes(String tableName, Set<byte[]> rowKeys) {
boolean flag = false;
try {
HTable table = (HTable) conn.getTable(TableName.valueOf(tableName));
HTable table = (HTable) getConnection().getTable(TableName.valueOf(tableName));
List<Delete> deletes = new ArrayList<>();
for (byte[] rowKey : rowKeys) {
Delete delete = new Delete(rowKey);
@@ -157,7 +178,14 @@ public class HbaseTemplate {
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();
}
}
}

View File

@@ -8,8 +8,8 @@ import java.sql.Timestamp;
import java.util.Date;
import org.apache.hadoop.hbase.util.Bytes;
import org.joda.time.DateTime;
import com.alibaba.otter.canal.client.adapter.support.Util;
import com.google.common.math.LongMath;
/**
@@ -130,8 +130,10 @@ public class PhTypeUtil {
} else if (b[0] == 0) {
v = false;
}
} else if (phType == PhType.TIME || phType == PhType.DATE) {
v = new Date(decodeLong(b, 0));
} 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);
@@ -389,7 +391,7 @@ public class PhTypeUtil {
String dateStr = (String) v;
Date date;
try {
date = parseDatetime(dateStr);
date = Util.parseDate(dateStr);
if (date != null) {
encodeLong(date.getTime(), b, 0);
}
@@ -418,7 +420,7 @@ public class PhTypeUtil {
String dateStr = (String) v;
Date date;
try {
date = parseDatetime(dateStr);
date = Util.parseDate(dateStr);
if (date != null) {
encodeUnsignedLong(date.getTime(), b, 0);
}
@@ -599,22 +601,9 @@ public class PhTypeUtil {
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));
throw new RuntimeException(
"Expected length of at least " + requiredLength + " bytes, but had " + (b.length - offset));
}
}
private static Date parseDatetime(String dateStr) {
Date date = null;
int len = dateStr.length();
if (len == 10 && dateStr.charAt(4) == '-' && dateStr.charAt(7) == '-') {
date = new DateTime(dateStr).toDate();
} else if (len == 8 && dateStr.charAt(2) == ':' && dateStr.charAt(5) == ':') {
date = new DateTime("T" + dateStr).toDate();
} else if (len >= 19 && dateStr.charAt(4) == '-' && dateStr.charAt(7) == '-' && dateStr.charAt(13) == ':'
&& dateStr.charAt(16) == ':') {
date = new DateTime(dateStr.replace(" ", "T")).toDate();
}
return date;
}
}

View File

@@ -1,7 +1,8 @@
dataSourceKey: defaultDS
destination: example
groupId: g1
hbaseMapping:
mode: PHOENIX #NATIVE #STRING
destination: example
mode: STRING #NATIVE #PHOENIX
database: mytest # 数据库名
table: person2 # 数据库表名
hbaseTable: MYTEST.PERSON2 # HBase表名
@@ -11,14 +12,14 @@ hbaseMapping:
#rowKey: id,type # 复合字段rowKey不能和columns中的rowKey重复
columns:
# 数据库字段:HBase对应字段
id: ROWKEY$UNSIGNED_LONG
id: ROWKEY LEN:15
name: NAME
email: EMAIL
type: $DECIMAL
c_time: C_TIME$UNSIGNED_TIMESTAMP
birthday: BIRTHDAY$DATE
excludeColumns:
- lat # 忽略字段
type:
c_time: C_TIME
birthday: BIRTHDAY
# excludeColumns:
# - lat # 忽略字段
# -- NATIVE类型
# $DEFAULT
@@ -57,4 +58,4 @@ hbaseMapping:
# $UNSIGNED_TIMESTAMP 对应PHOENIX里的UNSIGNED_TIMESTAMP 12字节
# $VARCHAR 对应PHOENIX里的VARCHAR 动态长度
# $VARBINARY 对应PHOENIX里的VARBINARY 动态长度
# $DECIMAL 对应PHOENIX里的DECIMAL 动态长度
# $DECIMAL 对应PHOENIX里的DECIMAL 动态长度

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.1</version>
<version>1.1.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.alibaba.otter</groupId>
@@ -32,7 +32,7 @@
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal.client</artifactId>
<version>1.1.1</version>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
@@ -46,7 +46,12 @@
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
@@ -66,6 +71,28 @@
<version>1.1.1</version>
</dependency>
<!-- jdbc -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.40</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.1.4</version>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.3</version>
</dependency>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>7.0.0.jre8</version>
</dependency>
<!-- outer adapter jar with dependencies-->
<dependency>
<groupId>com.alibaba.otter</groupId>
@@ -78,7 +105,7 @@
</exclusion>
</exclusions>
<classifier>jar-with-dependencies</classifier>
<optional>true</optional>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba.otter</groupId>
@@ -91,62 +118,38 @@
</exclusion>
</exclusions>
<classifier>jar-with-dependencies</classifier>
<optional>true</optional>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>client-adapter.elasticsearch</artifactId>
<version>${project.version}</version>
<exclusions>
<exclusion>
<artifactId>*</artifactId>
<groupId>*</groupId>
</exclusion>
</exclusions>
<classifier>jar-with-dependencies</classifier>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>client-adapter.rdb</artifactId>
<version>${project.version}</version>
<exclusions>
<exclusion>
<artifactId>*</artifactId>
<groupId>*</groupId>
</exclusion>
</exclusions>
<classifier>jar-with-dependencies</classifier>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>canal-adapter-launcher</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.0.1.RELEASE</version>
<configuration>
<excludes>
<exclude>
<groupId>com.alibaba.otter</groupId>
<artifactId>client-adapter.hbase</artifactId>
</exclude>
<exclude>
<groupId>com.alibaba.otter</groupId>
<artifactId>client-adapter.logger</artifactId>
</exclude>
</excludes>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</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}/target/config" overwrite="true">
<fileset dir="${project.basedir}/src/main/resources" erroronmissingdir="true">
<include name="*.yml" />
</fileset>
</copy>
<copy todir="${project.basedir}/target/config/hbase" overwrite="true">
<fileset dir="${project.basedir}/../hbase/src/main/resources/hbase" erroronmissingdir="true">
<include name="*.yml" />
</fileset>
</copy>
</tasks>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
@@ -160,15 +163,81 @@
</goals>
<configuration>
<includeClassifiers>jar-with-dependencies</includeClassifiers>
<outputDirectory>${project.basedir}/target/lib</outputDirectory>
<outputDirectory>${project.basedir}/target/canal-adapter/plugin</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2.1</version>
<executions>
<execution>
<id>assemble</id>
<goals>
<goal>single</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<attach>false</attach>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>com.alibaba.otter.canal.adapter.launcher.CanalAdapterApplication</mainClass>
</manifest>
</archive>
<excludes>
<exclude>**/*.properties</exclude>
<exclude>**/*.xml</exclude>
<exclude>**/*.yml</exclude>
<exclude>static/**</exclude>
<exclude>templates/**</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
<property>
<name>env</name>
<value>!release</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptors>
<descriptor>${basedir}/src/main/assembly/dev.xml</descriptor>
</descriptors>
<finalName>canal-adapter</finalName>
<outputDirectory>${project.build.directory}</outputDirectory>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>release</id>
<activation>
@@ -183,22 +252,12 @@
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<descriptors>
<descriptor>${basedir}/src/main/assembly/release.xml</descriptor>
</descriptors>
<finalName>${project.artifactId}-${project.version}</finalName>
<finalName>canal.adapter-${project.version}</finalName>
<outputDirectory>${project.basedir}/../../target</outputDirectory>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

View File

@@ -0,0 +1,69 @@
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
<id>dist</id>
<formats>
<format>dir</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>.</directory>
<outputDirectory>/</outputDirectory>
<includes>
<include>README*</include>
</includes>
</fileSet>
<fileSet>
<directory>./src/main/bin</directory>
<outputDirectory>bin</outputDirectory>
<includes>
<include>**/*</include>
</includes>
<fileMode>0755</fileMode>
</fileSet>
<fileSet>
<directory>./src/main/resources</directory>
<outputDirectory>/conf</outputDirectory>
<includes>
<include>**/*</include>
</includes>
</fileSet>
<fileSet>
<directory>../elasticsearch/src/main/resources/es</directory>
<outputDirectory>/conf/es</outputDirectory>
<includes>
<include>**/*</include>
</includes>
</fileSet>
<fileSet>
<directory>../hbase/src/main/resources/hbase</directory>
<outputDirectory>/conf/hbase</outputDirectory>
<includes>
<include>**/*</include>
</includes>
</fileSet>
<fileSet>
<directory>../rdb/src/main/resources/</directory>
<outputDirectory>/conf</outputDirectory>
<excludes>
<exclude>META-INF/**</exclude>
</excludes>
</fileSet>
<fileSet>
<directory>target</directory>
<outputDirectory>logs</outputDirectory>
<excludes>
<exclude>**/*</exclude>
</excludes>
</fileSet>
</fileSets>
<dependencySets>
<dependencySet>
<outputDirectory>lib</outputDirectory>
<excludes>
<exclude>junit:junit</exclude>
</excludes>
</dependencySet>
</dependencySets>
</assembly>

View File

@@ -1,34 +1,74 @@
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
<id>canal-adapter</id>
<id>dist</id>
<formats>
<format>tar.gz</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>${project.build.directory}</directory>
<directory>.</directory>
<outputDirectory>/</outputDirectory>
<includes>
<include>*.jar</include>
<include>README*</include>
</includes>
</fileSet>
<fileSet>
<directory>${project.build.directory}/lib</directory>
<outputDirectory>/lib/</outputDirectory>
<directory>./src/main/bin</directory>
<outputDirectory>bin</outputDirectory>
<includes>
<include>*.jar</include>
<include>**/*</include>
</includes>
<fileMode>0755</fileMode>
</fileSet>
<fileSet>
<directory>./src/main/resources</directory>
<outputDirectory>/conf</outputDirectory>
<includes>
<include>**/*</include>
</includes>
</fileSet>
<fileSet>
<directory>${project.build.directory}/config</directory>
<outputDirectory>/config/</outputDirectory>
<directory>../elasticsearch/src/main/resources/es</directory>
<outputDirectory>/conf/es</outputDirectory>
<includes>
<include>**</include>
<include>**/*</include>
</includes>
</fileSet>
<fileSet>
<directory>../hbase/src/main/resources/hbase</directory>
<outputDirectory>/conf/hbase</outputDirectory>
<includes>
<include>**/*</include>
</includes>
</fileSet>
<fileSet>
<directory>../rdb/src/main/resources/</directory>
<outputDirectory>/conf</outputDirectory>
<excludes>
<exclude>META-INF/**</exclude>
</excludes>
</fileSet>
<fileSet>
<directory>target</directory>
<outputDirectory>logs</outputDirectory>
<excludes>
<exclude>**/*</exclude>
</excludes>
</fileSet>
<fileSet>
<directory>${project.basedir}/target/canal-adapter/plugin</directory>
<outputDirectory>/plugin/</outputDirectory>
</fileSet>
</fileSets>
<baseDirectory>/</baseDirectory>
<dependencySets>
<dependencySet>
<outputDirectory>lib</outputDirectory>
<excludes>
<exclude>junit:junit</exclude>
</excludes>
</dependencySet>
</dependencySets>
</assembly>

View File

@@ -0,0 +1,5 @@
#!/bin/bash
sh stop.sh
sh startup.sh

View File

@@ -0,0 +1,22 @@
@echo off
@if not "%ECHO%" == "" echo %ECHO%
@if "%OS%" == "Windows_NT" setlocal
set ENV_PATH=.\
if "%OS%" == "Windows_NT" set ENV_PATH=%~dp0%
set conf_dir=%ENV_PATH%\..\conf
set CLASSPATH=%conf_dir%
set CLASSPATH=%conf_dir%\..\lib\*;%CLASSPATH%
set JAVA_MEM_OPTS= -Xms128m -Xmx512m -XX:PermSize=128m
set JAVA_OPTS_EXT= -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dapplication.codeset=UTF-8 -Dfile.encoding=UTF-8
set ADAPTER_OPTS= -DappName=canal-adapter
set JAVA_OPTS= %JAVA_MEM_OPTS% %JAVA_OPTS_EXT% %ADAPTER_OPTS%
set CMD_STR= java %JAVA_OPTS% -classpath "%CLASSPATH%" com.alibaba.otter.canal.adapter.launcher.CanalAdapterApplication
echo start cmd : %CMD_STR%
java %JAVA_OPTS% -classpath "%CLASSPATH%" com.alibaba.otter.canal.adapter.launcher.CanalAdapterApplication

View File

@@ -0,0 +1,83 @@
#!/bin/bash
current_path=`pwd`
case "`uname`" in
Linux)
bin_abs_path=$(readlink -f $(dirname $0))
;;
*)
bin_abs_path=`cd $(dirname $0); pwd`
;;
esac
base=${bin_abs_path}/..
export LANG=en_US.UTF-8
export BASE=$base
if [ -f $base/bin/adapter.pid ] ; then
echo "found adapter.pid , Please run stop.sh first ,then startup.sh" 2>&2
exit 1
fi
if [ ! -d $base/logs ] ; then
mkdir -p $base/logs
fi
## set java path
if [ -z "$JAVA" ] ; then
JAVA=$(which java)
fi
ALIBABA_JAVA="/usr/alibaba/java/bin/java"
TAOBAO_JAVA="/opt/taobao/java/bin/java"
if [ -z "$JAVA" ]; then
if [ -f $ALIBABA_JAVA ] ; then
JAVA=$ALIBABA_JAVA
elif [ -f $TAOBAO_JAVA ] ; then
JAVA=$TAOBAO_JAVA
else
echo "Cannot find a Java JDK. Please set either set JAVA or put java (>=1.5) in your PATH." 2>&2
exit 1
fi
fi
case "$#"
in
0 )
;;
2 )
if [ "$1" = "debug" ]; then
DEBUG_PORT=$2
DEBUG_SUSPEND="n"
JAVA_DEBUG_OPT="-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=$DEBUG_PORT,server=y,suspend=$DEBUG_SUSPEND"
fi
;;
* )
echo "THE PARAMETERS MUST BE TWO OR LESS.PLEASE CHECK AGAIN."
exit;;
esac
str=`file -L $JAVA | grep 64-bit`
if [ -n "$str" ]; then
JAVA_OPTS="-server -Xms2048m -Xmx3072m -Xmn1024m -XX:SurvivorRatio=2 -XX:PermSize=96m -XX:MaxPermSize=256m -Xss256k -XX:-UseAdaptiveSizePolicy -XX:MaxTenuringThreshold=15 -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:+HeapDumpOnOutOfMemoryError"
else
JAVA_OPTS="-server -Xms1024m -Xmx1024m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:MaxPermSize=128m "
fi
JAVA_OPTS=" $JAVA_OPTS -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8"
ADAPTER_OPTS="-DappName=canal-adapter"
for i in $base/lib/*;
do CLASSPATH=$i:"$CLASSPATH";
done
CLASSPATH="$base/conf:$CLASSPATH";
echo "cd to $bin_abs_path for workaround relative path"
cd $bin_abs_path
echo CLASSPATH :$CLASSPATH
$JAVA $JAVA_OPTS $JAVA_DEBUG_OPT $ADAPTER_OPTS -classpath .:$CLASSPATH com.alibaba.otter.canal.adapter.launcher.CanalAdapterApplication 1>>/dev/null 2>&1 &
echo $! > $base/bin/adapter.pid
echo "cd to $current_path for continue"
cd $current_path

View File

@@ -0,0 +1,65 @@
#!/bin/bash
cygwin=false;
linux=false;
case "`uname`" in
CYGWIN*)
cygwin=true
;;
Linux*)
linux=true
;;
esac
get_pid() {
STR=$1
PID=$2
if $cygwin; then
JAVA_CMD="$JAVA_HOME\bin\java"
JAVA_CMD=`cygpath --path --unix $JAVA_CMD`
JAVA_PID=`ps |grep $JAVA_CMD |awk '{print $1}'`
else
if $linux; then
if [ ! -z "$PID" ]; then
JAVA_PID=`ps -C java -f --width 1000|grep "$STR"|grep "$PID"|grep -v grep|awk '{print $2}'`
else
JAVA_PID=`ps -C java -f --width 1000|grep "$STR"|grep -v grep|awk '{print $2}'`
fi
else
if [ ! -z "$PID" ]; then
JAVA_PID=`ps aux |grep "$STR"|grep "$PID"|grep -v grep|awk '{print $2}'`
else
JAVA_PID=`ps aux |grep "$STR"|grep -v grep|awk '{print $2}'`
fi
fi
fi
echo $JAVA_PID;
}
base=`dirname $0`/..
pidfile=$base/bin/adapter.pid
if [ ! -f "$pidfile" ];then
echo "canal-adapter is not running. exists"
exit
fi
pid=`cat $pidfile`
if [ "$pid" == "" ] ; then
pid=`get_pid "appName=canal-adapter"`
fi
echo -e "`hostname`: stopping canal $pid ... "
kill $pid
LOOPS=0
while (true);
do
gpid=`get_pid "appName=canal-adapter" "$pid"`
if [ "$gpid" == "" ] ; then
echo "Oook! cost:$LOOPS"
`rm $pidfile`
break;
fi
let LOOPS=LOOPS+1
sleep 1
done

View File

@@ -1,7 +1,8 @@
package com.alibaba.otter.canal.adapter.launcher;
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
/**
* 启动入口
@@ -13,6 +14,8 @@ import org.springframework.boot.builder.SpringApplicationBuilder;
public class CanalAdapterApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(CanalAdapterApplication.class).run(args);
SpringApplication application = new SpringApplication(CanalAdapterApplication.class);
application.setBannerMode(Banner.Mode.OFF);
application.run(args);
}
}

View File

@@ -27,7 +27,7 @@ public class EtlLock {
private static final Map<String, InterProcessMutex> DISTRIBUTED_LOCK = new ConcurrentHashMap<>();
private static Mode mode = Mode.LOCAL;
private Mode mode = Mode.LOCAL;
@Resource
private CuratorClient curatorClient;

View File

@@ -34,7 +34,7 @@ public class SyncSwitch {
private static final Map<String, BooleanMutex> DISTRIBUTED_LOCK = new ConcurrentHashMap<>();
private static Mode mode = Mode.LOCAL;
private Mode mode = Mode.LOCAL;
@Resource
private AdapterCanalConfig adapterCanalConfig;
@@ -64,11 +64,12 @@ public class SyncSwitch {
}
}
@SuppressWarnings("resource")
private synchronized void startListen(String destination, BooleanMutex mutex) {
try {
String path = SYN_SWITCH_ZK_NODE + destination;
CuratorFramework curator = curatorClient.getCurator();
final NodeCache nodeCache = new NodeCache(curator, path);
NodeCache nodeCache = new NodeCache(curator, path);
nodeCache.start();
nodeCache.getListenable().addListener(() -> initMutex(curator, destination, mutex));
} catch (Exception e) {
@@ -164,20 +165,20 @@ public class SyncSwitch {
}
}
public Boolean status(String destination) {
public boolean status(String destination) {
if (mode == Mode.LOCAL) {
BooleanMutex mutex = LOCAL_LOCK.get(destination);
if (mutex != null) {
return mutex.state();
} else {
return null;
return false;
}
} else {
BooleanMutex mutex = DISTRIBUTED_LOCK.get(destination);
if (mutex != null) {
return mutex.state();
} else {
return null;
return false;
}
}
}

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