Compare commits

..

69 Commits

Author SHA1 Message Date
jianghang.loujh
fb6981356f fixed mariadb 10.x 2022-09-13 22:28:49 +08:00
jianghang.loujh
c055737e24 support druid 1.2.12 2022-09-13 15:03:30 +08:00
jianghang.loujh
ef7e21e4b2 support jdk11 2022-09-01 18:27:19 +08:00
jianghang.loujh
ce916ee407 ignore compression event 2022-09-01 18:27:08 +08:00
jianghang.loujh
92758b8c87 fixed issue #4308 , support query_log_event for maraiadb 10.10.1 2022-09-01 17:35:29 +08:00
jianghang.loujh
7585d9c98b fixed issue #4225 , support mysql version >= 8.0.26 heartbeat v2 2022-09-01 16:14:55 +08:00
jianghang.loujh
9d37bb87b7 fixed issue #4243 , support auto register for cluster = null 2022-09-01 13:31:01 +08:00
jianghang.loujh
2faecec749 fixed issue #4266 , typo 2022-09-01 13:02:02 +08:00
jianghang.loujh
0ef7b28c03 fixed 4334 , support jdk8/jdk11 2022-09-01 11:15:39 +08:00
dataccs
41bf3e2289 修复Canal指定时间戳启动失效,总是从最新的点位开始同步问题 (#4348)
* 支持用户自定义的CanalAlarmHandler

* RowsLogEvent增加对TableMapLogEvent判空检查,防止NPE异常

* 修复Canal指定时间戳启动失效,总是从最新的点位开始同步问题, issue: #4347

Co-authored-by: 云时 <mingya.wmy@alibaba-inc.com>
2022-08-24 14:21:47 +08:00
tianpeidong
4d4199c801 fix destination not encoded (#4279) 2022-08-11 11:31:28 +08:00
zhangxunwei
b390e74a18 docs: add nodejs canal client support (#4260)
Co-authored-by: zhangxunwei <zhangxunwei@dxy.cn>
2022-08-11 10:48:54 +08:00
ChanaLii
72c0e24cfd fix issues#4328 (#4329) 2022-08-11 10:44:01 +08:00
gongchangyou
3dc86330a8 fix bug BASE TABLE as table name (#4217) 2022-08-11 10:41:01 +08:00
rewerma
eee73dc4b1 optimize YAML config loader (#4332) 2022-08-01 20:15:14 +08:00
jianghang.loujh
a96db39420 add polardb-x parser test 2022-06-13 09:39:29 +08:00
温绍锦
bac1d543f1 update fastjson & druid version (#4224)
* 修复升级2.0.4导致兼容的问题

* update druid & fastjson version
2022-06-13 09:31:46 +08:00
jianghang.loujh
753fad6639 fixed issue #4216 , support polardb-x show tables 2022-06-10 13:36:39 +08:00
jianghang.loujh
bc4dceea1a fixed issue #4216 , support polardb-x show databases 2022-06-10 11:37:52 +08:00
温绍锦
7aaee7b439 修复升级2.0.4导致兼容的问题 (#4202) 2022-05-30 10:29:38 +08:00
温绍锦
e19bf1ecff Fastjson2 compatible fix (#4189)
* update fastjson2 version, 2.0.2 -> 2.0.3

* update fastjson & druid version

* bug fix for fastjson2 compatible
2022-05-25 10:22:26 +08:00
jianghang.loujh
a99dc0cbe8 fixed testcase 2022-05-24 11:18:40 +08:00
jianghang.loujh
7b8ff04d20 [maven-release-plugin] prepare for next development iteration 2022-05-24 10:26:12 +08:00
jianghang.loujh
7ee246ca5a [maven-release-plugin] prepare release canal-1.1.6 2022-05-24 10:26:06 +08:00
jianghang.loujh
54c645f315 fixed test failed 2022-05-24 10:12:26 +08:00
jianghang.loujh
6c81ab6fdc fixed compiler error 2022-05-24 10:08:02 +08:00
jianghang.loujh
99aefaa118 support mysql 8.0 transaction_payload_event 2022-05-23 23:23:52 +08:00
jianghang.loujh
e2248b3d35 update phoenix pom.xml 2022-05-23 19:30:19 +08:00
jianghang.loujh
eef1ee85fb fixed issue #4014, support java8 & java11 G1 GC 2022-05-23 18:23:31 +08:00
温绍锦
7dfec19c68 update druid & fastjson2 version (#4183)
* update fastjson2 version, 2.0.2 -> 2.0.3

* update fastjson & druid version
2022-05-23 10:03:41 +08:00
jianghang.loujh
05a52f1bd3 upgrade log4j version 2022-05-20 16:58:20 +08:00
jianghang.loujh
fb0f274ba8 fixed logger 2022-05-20 16:09:53 +08:00
jianghang.loujh
d71a208123 fixed issue #4178 , support -00:00:00.12 2022-05-20 10:37:53 +08:00
jianghang.loujh
656a1d23c2 instance.properties demo 2022-05-19 19:56:50 +08:00
jianghang.loujh
a574b91fdf fixed #3976 , support LogEvent.MYSQL_TYPE_TYPED_ARRAY 2022-05-19 19:53:54 +08:00
jianghang.loujh
e41e903984 fixed issue #3538 , compatiable mysql8.0 filed meta 2022-05-17 17:17:32 +08:00
jianghang.loujh
4a22d72ef4 fixed issue #4018, fixed CanalInstanceWithManager thread-safe 2022-05-16 19:46:34 +08:00
jianghang.loujh
b7eff723a3 fixed issue #4027, CanalServerWithEmbedded.get support newList 2022-05-16 19:41:46 +08:00
jianghang.loujh
9b9cfe4037 fixed issue #4152, TableMetaTSDB support canal.instance.filter.query.ddl=true 2022-05-16 17:25:43 +08:00
jianghang.loujh
ee51f338e0 fixed outAdapterkey autoGen 2022-05-13 15:13:14 +08:00
zhuchao941
4315f203a2 repair issue #3401 (#3923)
* 解决OutAdapter单例导致的问题,如 1.多个es7 outAdapter的多线程问题(共享同一个es bulk request),2.多个es7 outAdapter共用一份es配置问题

* 1. 优化adapter和监听器之间的代码结构
2. 监听器只处理匹配的配置文件
3. bugfix-es adapter配置文件回调时漏写esVersion
理论上一个adapter实例对应一个config配置文件

Co-authored-by: agapple <jianghang.loujh@alibaba-inc.com>
2022-05-13 15:02:06 +08:00
jianghang.loujh
b12945d4a9 format yml 2022-05-13 14:39:57 +08:00
Treeyh
885f05fb6a 支持定义rocketmq client日志路径,及修复一些bug (#3229)
* 修复接入阿里云RocketMq no route info for this topic 问题

* 支持deployer同步rocketmq时定义rocketmq client日志路径

* 支持deployer同步rocketmq时定义rocketmq client日志路径

* 回退guava版本号,解决adapter的java.lang.NoSuchMethodError: com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor()异常

* 解决adapter启动报com.alibaba.druid.pool.DruidDataSource cannot be cast to com.alibaba.druid.pool.DruidDataSource异常。

* 支持rocketmq client 配置日志路径

Co-authored-by: hai.yu <hai.yu@snowballtech.com>
Co-authored-by: agapple <jianghang.loujh@alibaba-inc.com>
2022-05-13 14:39:29 +08:00
ChengTie(鐵手)
591126ee6b update (#3326) 2022-05-13 14:37:17 +08:00
He Wang
af432bf10a fix data truncated for column issue for type year (#3866) 2022-05-13 14:33:06 +08:00
jumao
b532e90ff4 ESAdapter Support the same instance cross -library query (#4161)
* ESAdapter Support the same instance cross -library query

* fix null point ex
2022-05-13 14:31:09 +08:00
xiaoyu.zhang
e48bc35f35 通过索引别名获取mapping,返回数据为真实索引,而不是别名,导致mappings.get(index).get(type)报空指针异常 (#4122) 2022-05-13 14:28:53 +08:00
He Wang
11a5f2a0fa add druid stat filter (#3985) 2022-05-13 14:24:37 +08:00
He Wang
c775478a56 set backtick by DbType (#3984)
Co-authored-by: agapple <jianghang.loujh@alibaba-inc.com>
2022-05-13 14:23:40 +08:00
He Wang
bd1f91cd9c fix sync order in RdbMirrorDbSyncService (#3928) 2022-05-13 14:19:40 +08:00
jianghang.loujh
67e85d201c remove client adapter Docker 2022-05-13 12:46:12 +08:00
wanglei
6965726dfe fix bug #2023 (#3871)
* fix bug #2023

* 配置canal-adapter,docker生成脚本
2022-05-13 12:39:43 +08:00
willmao
c0001afe8f use hostname if register name not set (#3968) 2022-05-13 12:24:33 +08:00
xiayingfeng
977031a666 Improve MAVEN build Performance (#4055) 2022-05-13 12:23:33 +08:00
zhangshaoning
bd3224eea1 Upgrade reference log4j package version (#4017) 2022-05-13 12:22:45 +08:00
jianghang.loujh
33733aef25 fixed compatible and code format 2022-05-13 11:59:57 +08:00
zhangjukai
25be0259a4 Some tweaks for pulsarmq-connector (#4060)
* 1. Add the com.alibaba.otter.canal.connector.core.spi.CanalMsgConsumer file to the META-INF/canal directory under the pulsarmq-connector project
2. Introduce pulsar-client-admin for Canal to automatically create multi-partition topics
3. Add the judgment that roleToken is null
4. The disconnect method in CanalPulsarMQConsumer removes this.pulsarMQConsumer.unsubscribe();, this code will cause data loss during stop
5. When getting Pulsar messages, they are all processed as flat messages, because CanalMessageSerializerUtil.deserializer(data) will deserialize exceptions
6. Use groupId as subscriptName, without the pulsarmq.subscriptName parameter, the entire adapter will be the same subscriber name using pulsarmq.subscriptName

* 逻辑优化

* 代码优化

* 针对pulsar的调整

* 恢复代码

* 恢复代码

* 处理Producer失效的情况
2022-05-13 11:41:50 +08:00
liteng2430
8114d77a20 Add verification: the user can set a password with a length of less than 6 digits when updating the password, but the password length must be greater than or equal to 6 digits when logging in to canal admin, so the length verification function when updating the login password is added (#4103) 2022-05-13 11:34:36 +08:00
ZhangJian He
571e7f4e25 fix ojdbc6 wrong corrinator (#4132) 2022-05-13 11:33:42 +08:00
温绍锦
ead91433d7 fastjson update 1.x to 2.0.2 (#4160) 2022-05-13 11:32:47 +08:00
xiaoma20082008
bac016f89e <feat>: support visibility columns (#3976) 2022-05-13 11:30:19 +08:00
willmao
8804647c30 avoid print password when logging (#3969) 2022-05-13 11:28:05 +08:00
mclubing
2623d08b8c rdb配置不支持yaml后缀文件 (#3964)
增加rdb配置支持yaml后缀文件
2022-05-13 11:26:04 +08:00
wuare
622fed84ce fixed logger name (#3887) 2022-05-13 11:24:00 +08:00
He Wang
9f5e8fd1c4 rollback guava version in adapter module (#3880) 2022-05-13 11:23:30 +08:00
Junwang Zhao
88d472ef1e move protocol test to protocol test directory (#3864)
1. move the protocol test to right directory
2. fix wrong string format params count

Signed-off-by: 元组 <zhaojunwang.zjw@alibaba-inc.com>
2022-05-13 11:16:06 +08:00
Aiden.Ao
2987d08161 fix issue #3144 (#3712) 2022-05-13 11:09:09 +08:00
tuple
b54bea5e33 polish the code thus make it more stable (#3855)
Signed-off-by: 元组 <zhaojunwang.zjw@alibaba-inc.com>
2021-10-11 14:46:22 +08:00
jianghang.loujh
c42ba95eed fixed pulsarmq package 2021-10-09 18:06:34 +08:00
176 changed files with 2369 additions and 5541 deletions

View File

@@ -85,6 +85,7 @@ canal 特别设计了 client-server 模式,交互协议使用 protobuf 3.0 , c
- canal php客户端: [https://github.com/xingwenge/canal-php](https://github.com/xingwenge/canal-php)
- canal Python客户端:[https://github.com/haozi3156666/canal-python](https://github.com/haozi3156666/canal-python)
- canal Rust客户端:[https://github.com/laohanlinux/canal-rs](https://github.com/laohanlinux/canal-rs)
- canal Nodejs客户端:[https://github.com/marmot-z/canal-nodejs](https://github.com/marmot-z/canal-nodejs)
canal 作为 MySQL binlog 增量获取和解析工具,可将变更记录投递到 MQ 系统中,比如 Kafka/RocketMQ,可以借助于 MQ 的多语言能力

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>canal.admin</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.6-SNAPSHOT</version>
<version>1.1.7-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -59,7 +59,7 @@ export default {
canalInstanceDetail(this.$route.query.id).then(response => {
const data = response.data
this.form.id = data.id
this.form.name = data.name + '/instance.propertios'
this.form.name = data.name + '/instance.properties'
this.form.content = data.content
this.form.clusterServerId = data.clusterServerId
})

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>canal.admin</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.6-SNAPSHOT</version>
<version>1.1.7-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>canal.admin-web</artifactId>

View File

@@ -56,14 +56,29 @@ in
exit;;
esac
JavaVersion=`$JAVA -version 2>&1 |awk 'NR==1{ gsub(/"/,""); print $3 }' | awk -F '.' '{print $1}'`
str=`file -L $JAVA | grep 64-bit`
if [ -n "$str" ]; then
JAVA_OPTS="-server -Xms2048m -Xmx3072m"
JAVA_OPTS="$JAVA_OPTS -Xss256k -XX:+AggressiveOpts -XX:-UseBiasedLocking -XX:-OmitStackTraceInFastThrow -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$base/logs"
if [ $JavaVersion -ge 11 ] ; then
#JAVA_OPTS="$JAVA_OPTS -Xlog:gc*:$base_log/gc.log:time "
JAVA_OPTS="$JAVA_OPTS"
else
JAVA_OPTS="-server -Xms1024m -Xmx1024m"
#JAVA_OPTS="$JAVA_OPTS -Xloggc:$base/logs/canal/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime"
JAVA_OPTS="$JAVA_OPTS -XX:+UseFastAccessorMethods -XX:+PrintAdaptiveSizePolicy -XX:+PrintTenuringDistribution"
fi
if [ -n "$str" ]; then
if [ $JavaVersion -ge 11 ] ; then
# For G1
JAVA_OPTS="-server -Xms2g -Xmx3g -XX:+UseG1GC -XX:MaxGCPauseMillis=250 -XX:+UseGCOverheadLimit -XX:+ExplicitGCInvokesConcurrent $JAVA_OPTS"
else
JAVA_OPTS="-server -Xms2g -Xmx3g -Xmn1g -XX:SurvivorRatio=2 -XX:PermSize=96m -XX:MaxPermSize=256m -XX:MaxTenuringThreshold=15 -XX:+DisableExplicitGC $JAVA_OPTS"
fi
else
JAVA_OPTS="-server -Xms1024m -Xmx1024m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:MaxPermSize=128m $JAVA_OPTS"
fi
JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=250 -XX:+UseGCOverheadLimit -XX:+ExplicitGCInvokesConcurrent -XX:+PrintAdaptiveSizePolicy -XX:+PrintTenuringDistribution"
JAVA_OPTS=" $JAVA_OPTS -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8"
CANAL_OPTS="-DappName=canal-admin"
@@ -81,4 +96,4 @@ $JAVA $JAVA_OPTS $JAVA_DEBUG_OPT $CANAL_OPTS -classpath .:$CLASSPATH com.alibaba
echo $! > $base/bin/admin.pid
echo "cd to $current_path for continue"
cd $current_path
cd $current_path

View File

@@ -2,6 +2,7 @@ package com.alibaba.otter.canal.admin.connector;
import java.util.function.Function;
import org.assertj.core.util.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
@@ -26,7 +27,8 @@ public class SimpleAdminConnectors {
connector.connect();
return function.apply(connector);
} catch (Exception e) {
logger.error("connect to ip:{},port:{},user:{},password:{}, failed", ip, port, user, passwd);
logger.error("connect to ip:{},port:{},user:{},password:{}, failed",
ip, port, user, getDesensitizationPassword(passwd));
logger.error(e.getMessage());
} finally {
connector.disconnect();
@@ -34,4 +36,14 @@ public class SimpleAdminConnectors {
return null;
}
private static String getDesensitizationPassword(String password) {
String defaultPassword = "******";
if (Strings.isNullOrEmpty(password) || password.length() < 6) {
return defaultPassword;
} else {
return String.format("%s******", password.substring(0, 3));
}
}
}

View File

@@ -13,8 +13,8 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.otter.canal.admin.model.BaseModel;
import com.alibaba.otter.canal.admin.model.CanalCluster;
import com.alibaba.otter.canal.admin.model.NodeServer;

View File

@@ -21,6 +21,8 @@ public class UserServiceImpl implements UserService {
private static byte[] seeds = "canal is best!".getBytes();
private static final Integer PASSWORD_LENGTH = 6;
public User find4Login(String username, String password) {
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
return null;
@@ -43,6 +45,9 @@ public class UserServiceImpl implements UserService {
}
public void update(User user) {
if (user.getPassword().length() < PASSWORD_LENGTH) {
throw new ServiceException("The new password is too short,must more than 6 digits");
}
User userTmp = User.find.query().where().eq("username", user.getUsername()).findOne();
if (userTmp == null) {
throw new ServiceException();

View File

@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal.admin</artifactId>
<version>1.1.6-SNAPSHOT</version>
<version>1.1.7-SNAPSHOT</version>
<packaging>pom</packaging>
<name>canal admin module for otter ${project.version}</name>
<modules>
@@ -18,6 +18,7 @@
<java_source_version>1.8</java_source_version>
<java_target_version>1.8</java_target_version>
<file_encoding>UTF-8</file_encoding>
<log4j_version>2.17.0</log4j_version>
</properties>
<dependencyManagement>
@@ -91,6 +92,21 @@
<artifactId>ebean</artifactId>
<version>11.41.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j_version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4j_version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>${log4j_version}</version>
</dependency>
</dependencies>
</dependencyManagement>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.6-SNAPSHOT</version>
<version>1.1.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -23,29 +23,8 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
</dependency>
<dependency>
<groupId>joda-time</groupId>
@@ -55,10 +34,6 @@
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>

View File

@@ -1,155 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import org.springframework.util.PropertyPlaceholderHelper;
import org.springframework.util.StringUtils;
import com.alibaba.otter.canal.client.adapter.config.bind.PropertiesConfigurationFactory;
import com.alibaba.otter.canal.client.adapter.config.common.*;
/**
* 将yaml内容绑定到指定对象, 遵循spring yml的绑定规范
*
* @author reweerma 2019-2-1 上午9:14:02
* @version 1.0.0
*/
public class YmlConfigBinder {
/**
* 将当前内容绑定到指定对象
*
* @param content yml内容
* @param clazz 指定对象类型
* @return 对象
*/
public static <T> T bindYmlToObj(String content, Class<T> clazz) {
return bindYmlToObj(null, content, clazz, null);
}
/**
* 将当前内容绑定到指定对象并指定内容编码格式
*
* @param content yml内容
* @param clazz 指定对象类型
* @param charset yml内容编码格式
* @return 对象
*/
public static <T> T bindYmlToObj(String content, Class<T> clazz, String charset) {
return bindYmlToObj(null, content, clazz, charset);
}
/**
* 将当前内容指定前缀部分绑定到指定对象
*
* @param prefix 指定前缀
* @param content yml内容
* @param clazz 指定对象类型
* @return 对象
*/
public static <T> T bindYmlToObj(String prefix, String content, Class<T> clazz) {
return bindYmlToObj(prefix, content, clazz, null);
}
/**
* 将当前内容指定前缀部分绑定到指定对象并指定内容编码格式
*
* @param prefix 指定前缀
* @param content yml内容
* @param clazz 指定对象类型
* @param charset yml内容编码格式
* @return 对象
*/
public static <T> T bindYmlToObj(String prefix, String content, Class<T> clazz, String charset) {
return bindYmlToObj(prefix, content, clazz, charset, null);
}
/**
* 将当前内容指定前缀部分绑定到指定对象并用环境变量中的属性替换占位符, 例: 当前内容有属性 zkServers: ${zookeeper.servers}
* 在envProperties中有属性 zookeeper.servers:
* 192.168.0.1:2181,192.168.0.1:2181,192.168.0.1:2181 则当前内容 zkServers 会被替换为
* zkServers: 192.168.0.1:2181,192.168.0.1:2181,192.168.0.1:2181 注: 假设绑定的类中
* zkServers 属性是 List<String> 对象, 则会自动映射成List
*
* @param prefix 指定前缀
* @param content yml内容
* @param clazz 指定对象类型
* @param charset yml内容编码格式
* @return 对象
*/
public static <T> T bindYmlToObj(String prefix, String content, Class<T> clazz, String charset,
Properties baseProperties) {
try {
byte[] contentBytes;
if (charset == null) {
contentBytes = content.getBytes("UTF-8");
} else {
contentBytes = content.getBytes(charset);
}
YamlPropertySourceLoader propertySourceLoader = new YamlPropertySourceLoader();
Resource configResource = new ByteArrayResource(contentBytes);
PropertySource<?> propertySource = propertySourceLoader.load("manualBindConfig", configResource, null);
if (propertySource == null) {
return null;
}
Properties properties = new Properties();
Map<String, Object> propertiesRes = new LinkedHashMap<>();
if (!StringUtils.isEmpty(prefix) && !prefix.endsWith(".")) {
prefix = prefix + ".";
}
properties.putAll((Map<?, ?>) propertySource.getSource());
if (baseProperties != null) {
baseProperties.putAll(properties);
properties = baseProperties;
}
for (Map.Entry<?, ?> entry : ((Map<?, ?>) propertySource.getSource()).entrySet()) {
String key = (String) entry.getKey();
Object value = entry.getValue();
if (prefix != null) {
if (key != null && key.startsWith(prefix)) {
key = key.substring(prefix.length());
} else {
continue;
}
}
if (value instanceof String && ((String) value).contains("${") && ((String) value).contains("}")) {
PropertyPlaceholderHelper propertyPlaceholderHelper = new PropertyPlaceholderHelper("${", "}");
value = propertyPlaceholderHelper.replacePlaceholders((String) value, properties);
}
propertiesRes.put(key, value);
}
if (propertiesRes.isEmpty()) {
return null;
}
propertySource = new MapPropertySource(propertySource.getName(), propertiesRes);
T target = clazz.newInstance();
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(propertySource);
PropertiesConfigurationFactory<Object> factory = new PropertiesConfigurationFactory<>(target);
factory.setPropertySources(propertySources);
factory.setIgnoreInvalidFields(true);
factory.setIgnoreUnknownFields(true);
factory.bindPropertiesToTarget();
return target;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -1,97 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.bind;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* {@link PropertyNamePatternsMatcher} that matches when a property name exactly
* matches one of the given names, or starts with one of the given names
* followed by a delimiter. This implementation is optimized for frequent calls.
*
* @author Phillip Webb
* @since 1.2.0
*/
class DefaultPropertyNamePatternsMatcher implements PropertyNamePatternsMatcher {
private final char[] delimiters;
private final boolean ignoreCase;
private final String[] names;
protected DefaultPropertyNamePatternsMatcher(char[] delimiters, String... names){
this(delimiters, false, names);
}
protected DefaultPropertyNamePatternsMatcher(char[] delimiters, boolean ignoreCase, String... names){
this(delimiters, ignoreCase, new HashSet<>(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 (boolean b : match) {
if (b) {
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

@@ -1,31 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.bind;
import java.beans.PropertyEditorSupport;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* {@link PropertyNamePatternsMatcher} that matches when a property name exactly
* matches one of the given names, or starts with one of the given names
* followed by a delimiter. This implementation is optimized for frequent calls.
*
* @author Phillip Webb
* @since 1.2.0
*/
class InetAddressEditor extends PropertyEditorSupport {
@Override
public String getAsText() {
return ((InetAddress) getValue()).getHostAddress();
}
@Override
public void setAsText(String text) throws IllegalArgumentException {
try {
setValue(InetAddress.getByName(text));
} catch (UnknownHostException ex) {
throw new IllegalArgumentException("Cannot locate host", ex);
}
}
}

View File

@@ -1,52 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.bind;
import org.springframework.beans.PropertyValue;
import com.alibaba.otter.canal.client.adapter.config.common.PropertySource;
/**
* A {@link PropertyValue} that can provide information about its origin.
*
* @author Andy Wilkinson
*/
class OriginCapablePropertyValue extends PropertyValue {
private static final String ATTRIBUTE_PROPERTY_ORIGIN = "propertyOrigin";
private final PropertyOrigin origin;
OriginCapablePropertyValue(PropertyValue propertyValue){
this(propertyValue.getName(),
propertyValue.getValue(),
(PropertyOrigin) propertyValue.getAttribute(ATTRIBUTE_PROPERTY_ORIGIN));
}
OriginCapablePropertyValue(String name, Object value, String originName, PropertySource<?> originSource){
this(name, value, new PropertyOrigin(originSource, originName));
}
OriginCapablePropertyValue(String name, Object value, PropertyOrigin origin){
super(name, value);
this.origin = origin;
setAttribute(ATTRIBUTE_PROPERTY_ORIGIN, origin);
}
public PropertyOrigin getOrigin() {
return this.origin;
}
@Override
public String toString() {
String name = (this.origin != null ? this.origin.getName() : this.getName());
String source = (this.origin.getSource() != null ? this.origin.getSource().getName() : "unknown");
return "'" + name + "' from '" + source + "'";
}
public static PropertyOrigin getOrigin(PropertyValue propertyValue) {
if (propertyValue instanceof OriginCapablePropertyValue) {
return ((OriginCapablePropertyValue) propertyValue).getOrigin();
}
return new OriginCapablePropertyValue(propertyValue).getOrigin();
}
}

View File

@@ -1,27 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.bind;
import java.util.Collection;
import org.springframework.util.PatternMatchUtils;
/**
* {@link PropertyNamePatternsMatcher} that delegates to
* {@link PatternMatchUtils#simpleMatch(String[], String)}.
*
* @author Phillip Webb
* @since 1.2.0
*/
class PatternPropertyNamePatternsMatcher implements PropertyNamePatternsMatcher {
private final String[] patterns;
PatternPropertyNamePatternsMatcher(Collection<String> patterns){
this.patterns = (patterns != null ? patterns.toArray(new String[patterns.size()]) : new String[] {});
}
@Override
public boolean matches(String propertyName) {
return PatternMatchUtils.simpleMatch(this.patterns, propertyName);
}
}

View File

@@ -1,356 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.bind;
import java.beans.PropertyDescriptor;
import java.util.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.support.ResourceEditorRegistrar;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.core.convert.ConversionService;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.validation.*;
import com.alibaba.otter.canal.client.adapter.config.common.PropertySources;
/**
* Validate some {@link Properties} (or optionally
* {@link org.springframework.core.env.PropertySources}) by binding them to an
* object of a specified type and then optionally running a {@link Validator}
* over it.
*
* @param <T> the target type
* @author Dave Syer
*/
public class PropertiesConfigurationFactory<T> implements FactoryBean<T>, ApplicationContextAware, MessageSourceAware, InitializingBean {
private static final char[] EXACT_DELIMITERS = { '_', '.', '[' };
private static final char[] TARGET_NAME_DELIMITERS = { '_', '.' };
private static final Log logger = LogFactory.getLog(PropertiesConfigurationFactory.class);
private boolean ignoreUnknownFields = true;
private boolean ignoreInvalidFields;
private boolean exceptionIfInvalid = true;
private PropertySources propertySources;
private final T target;
private Validator validator;
private ApplicationContext applicationContext;
private MessageSource messageSource;
private boolean hasBeenBound = false;
private boolean ignoreNestedProperties = false;
private String targetName;
private ConversionService conversionService;
private boolean resolvePlaceholders = true;
/**
* Create a new {@link PropertiesConfigurationFactory} instance.
*
* @param target the target object to bind too
* @see #PropertiesConfigurationFactory(Class)
*/
public PropertiesConfigurationFactory(T target){
Assert.notNull(target, "target must not be null");
this.target = target;
}
/**
* Create a new {@link PropertiesConfigurationFactory} instance.
*
* @param type the target type
* @see #PropertiesConfigurationFactory(Class)
*/
@SuppressWarnings("unchecked")
public PropertiesConfigurationFactory(Class<?> type){
Assert.notNull(type, "type must not be null");
this.target = (T) BeanUtils.instantiate(type);
}
/**
* Flag to disable binding of nested properties (i.e. those with period
* separators in their paths). Can be useful to disable this if the name prefix
* is empty and you don't want to ignore unknown fields.
*
* @param ignoreNestedProperties the flag to set (default false)
*/
public void setIgnoreNestedProperties(boolean ignoreNestedProperties) {
this.ignoreNestedProperties = ignoreNestedProperties;
}
/**
* Set whether to ignore unknown fields, that is, whether to ignore bind
* parameters that do not have corresponding fields in the target object.
* <p>
* Default is "true". Turn this off to enforce that all bind parameters must
* have a matching field in the target object.
*
* @param ignoreUnknownFields if unknown fields should be ignored
*/
public void setIgnoreUnknownFields(boolean ignoreUnknownFields) {
this.ignoreUnknownFields = ignoreUnknownFields;
}
/**
* Set whether to ignore invalid fields, that is, whether to ignore bind
* parameters that have corresponding fields in the target object which are not
* accessible (for example because of null values in the nested path).
* <p>
* Default is "false". Turn this on to ignore bind parameters for nested objects
* in non-existing parts of the target object graph.
*
* @param ignoreInvalidFields if invalid fields should be ignored
*/
public void setIgnoreInvalidFields(boolean ignoreInvalidFields) {
this.ignoreInvalidFields = ignoreInvalidFields;
}
/**
* Set the target name.
*
* @param targetName the target name
*/
public void setTargetName(String targetName) {
this.targetName = targetName;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
/**
* Set the message source.
*
* @param messageSource the message source
*/
@Override
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
/**
* Set the property sources.
*
* @param propertySources the property sources
*/
public void setPropertySources(PropertySources propertySources) {
this.propertySources = propertySources;
}
/**
* Set the conversion service.
*
* @param conversionService the conversion service
*/
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
/**
* Set the validator.
*
* @param validator the validator
*/
public void setValidator(Validator validator) {
this.validator = validator;
}
/**
* Set a flag to indicate that an exception should be raised if a Validator is
* available and validation fails.
*
* @param exceptionIfInvalid the flag to set
* @deprecated as of 1.5, do not specify a {@link Validator} if validation
* should not occur
*/
@Deprecated
public void setExceptionIfInvalid(boolean exceptionIfInvalid) {
this.exceptionIfInvalid = exceptionIfInvalid;
}
/**
* Flag to indicate that placeholders should be replaced during binding. Default
* is true.
*
* @param resolvePlaceholders flag value
*/
public void setResolvePlaceholders(boolean resolvePlaceholders) {
this.resolvePlaceholders = resolvePlaceholders;
}
@Override
public void afterPropertiesSet() throws Exception {
bindPropertiesToTarget();
}
@Override
public Class<?> getObjectType() {
if (this.target == null) {
return Object.class;
}
return this.target.getClass();
}
@Override
public boolean isSingleton() {
return true;
}
@Override
public T getObject() throws Exception {
if (!this.hasBeenBound) {
bindPropertiesToTarget();
}
return this.target;
}
public void bindPropertiesToTarget() throws BindException {
Assert.state(this.propertySources != null, "PropertySources should not be null");
try {
if (logger.isTraceEnabled()) {
logger.trace("Property Sources: " + this.propertySources);
}
this.hasBeenBound = true;
doBindPropertiesToTarget();
} catch (BindException ex) {
if (this.exceptionIfInvalid) {
throw ex;
}
logger.error("Failed to load Properties validation bean. " + "Your Properties may be invalid.", ex);
}
}
private void doBindPropertiesToTarget() throws BindException {
RelaxedDataBinder dataBinder = (this.targetName != null ? new RelaxedDataBinder(this.target,
this.targetName) : new RelaxedDataBinder(this.target));
if (this.validator != null && this.validator.supports(dataBinder.getTarget().getClass())) {
dataBinder.setValidator(this.validator);
}
if (this.conversionService != null) {
dataBinder.setConversionService(this.conversionService);
}
dataBinder.setAutoGrowCollectionLimit(Integer.MAX_VALUE);
dataBinder.setIgnoreNestedProperties(this.ignoreNestedProperties);
dataBinder.setIgnoreInvalidFields(this.ignoreInvalidFields);
dataBinder.setIgnoreUnknownFields(this.ignoreUnknownFields);
customizeBinder(dataBinder);
if (this.applicationContext != null) {
ResourceEditorRegistrar resourceEditorRegistrar = new ResourceEditorRegistrar(this.applicationContext,
this.applicationContext.getEnvironment());
resourceEditorRegistrar.registerCustomEditors(dataBinder);
}
Iterable<String> relaxedTargetNames = getRelaxedTargetNames();
Set<String> names = getNames(relaxedTargetNames);
PropertyValues propertyValues = getPropertySourcesPropertyValues(names, relaxedTargetNames);
dataBinder.bind(propertyValues);
if (this.validator != null) {
dataBinder.validate();
}
checkForBindingErrors(dataBinder);
}
private Iterable<String> getRelaxedTargetNames() {
return (this.target != null
&& StringUtils.hasLength(this.targetName) ? new RelaxedNames(this.targetName) : null);
}
private Set<String> getNames(Iterable<String> prefixes) {
Set<String> names = new LinkedHashSet<>();
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<>();
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

@@ -1,24 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.bind;
/**
* Strategy interface used to check if a property name matches specific
* criteria.
*
* @author Phillip Webb
* @since 1.2.0
*/
interface PropertyNamePatternsMatcher {
PropertyNamePatternsMatcher ALL = propertyName -> true;
PropertyNamePatternsMatcher NONE = propertyName -> 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

@@ -1,30 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.bind;
import com.alibaba.otter.canal.client.adapter.config.common.PropertySource;
/**
* The origin of a property, specifically its source and its name before any
* prefix was removed.
*
* @author Andy Wilkinson
* @since 1.3.0
*/
public class PropertyOrigin {
private final PropertySource<?> source;
private final String name;
PropertyOrigin(PropertySource<?> source, String name){
this.name = name;
this.source = source;
}
public PropertySource<?> getSource() {
return this.source;
}
public String getName() {
return this.name;
}
}

View File

@@ -1,164 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.bind;
import org.springframework.core.convert.ConversionException;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.AbstractPropertyResolver;
import org.springframework.core.env.PropertyResolver;
import org.springframework.util.ClassUtils;
import com.alibaba.otter.canal.client.adapter.config.common.PropertySource;
import com.alibaba.otter.canal.client.adapter.config.common.PropertySources;
/**
* {@link PropertyResolver} implementation that resolves property values against
* an underlying set of {@link PropertySources}.
*
* @author Chris Beams
* @author Juergen Hoeller
* @see PropertySource
* @see PropertySources
* @see AbstractEnvironment
* @since 3.1
*/
public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
private final PropertySources propertySources;
/**
* Create a new resolver against the given property sources.
*
* @param propertySources the set of {@link PropertySource} objects to use
*/
public PropertySourcesPropertyResolver(PropertySources propertySources){
this.propertySources = propertySources;
}
@Override
public boolean containsProperty(String key) {
if (this.propertySources != null) {
for (PropertySource<?> propertySource : this.propertySources) {
if (propertySource.containsProperty(key)) {
return true;
}
}
}
return false;
}
@Override
public String getProperty(String key) {
return getProperty(key, String.class, true);
}
@Override
public <T> T getProperty(String key, Class<T> targetValueType) {
return getProperty(key, targetValueType, true);
}
@Override
protected String getPropertyAsRawString(String key) {
return getProperty(key, String.class, false);
}
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
if (this.propertySources != null) {
for (PropertySource<?> propertySource : this.propertySources) {
if (logger.isTraceEnabled()) {
logger
.trace("Searching for key '" + key + "' in PropertySource '" + propertySource.getName() + "'");
}
Object value = propertySource.getProperty(key);
if (value != null) {
if (resolveNestedPlaceholders && value instanceof String) {
value = resolveNestedPlaceholders((String) value);
}
logKeyFound(key, propertySource, value);
return convertValueIfNecessary(value, targetValueType);
}
}
}
if (logger.isDebugEnabled()) {
logger.debug("Could not find key '" + key + "' in any property source");
}
return null;
}
@Deprecated
public <T> Class<T> getPropertyAsClass(String key, Class<T> targetValueType) {
if (this.propertySources != null) {
for (PropertySource<?> propertySource : this.propertySources) {
if (logger.isTraceEnabled()) {
logger.trace(String.format("Searching for key '%s' in [%s]", key, propertySource.getName()));
}
Object value = propertySource.getProperty(key);
if (value != null) {
logKeyFound(key, propertySource, value);
Class<?> clazz;
if (value instanceof String) {
try {
clazz = ClassUtils.forName((String) value, null);
} catch (Exception ex) {
throw new PropertySourcesPropertyResolver.ClassConversionException((String) value,
targetValueType,
ex);
}
} else if (value instanceof Class) {
clazz = (Class<?>) value;
} else {
clazz = value.getClass();
}
if (!targetValueType.isAssignableFrom(clazz)) {
throw new PropertySourcesPropertyResolver.ClassConversionException(clazz, targetValueType);
}
@SuppressWarnings("unchecked")
Class<T> targetClass = (Class<T>) clazz;
return targetClass;
}
}
}
if (logger.isDebugEnabled()) {
logger.debug(String.format("Could not find key '%s' in any property source", key));
}
return null;
}
/**
* Log the given key as found in the given {@link PropertySource}, resulting in
* the given value.
* <p>
* The default implementation writes a debug log message with key and source. As
* of 4.3.3, this does not log the value anymore in order to avoid accidental
* logging of sensitive settings. Subclasses may override this method to change
* the log level and/or log message, including the property's value if desired.
*
* @param key the key found
* @param propertySource the {@code PropertySource} that the key has been found
* in
* @param value the corresponding value
* @since 4.3.1
*/
protected void logKeyFound(String key, PropertySource<?> propertySource, Object value) {
if (logger.isDebugEnabled()) {
logger.debug("Found key '" + key + "' in PropertySource '" + propertySource.getName()
+ "' with value of type " + value.getClass().getSimpleName());
}
}
@SuppressWarnings("serial")
@Deprecated
private static class ClassConversionException extends ConversionException {
public ClassConversionException(Class<?> actual, Class<?> expected){
super(String
.format("Actual type %s is not assignable to expected type %s", actual.getName(), expected.getName()));
}
public ClassConversionException(String actual, Class<?> expected, Exception ex){
super(
String
.format("Could not find/load class %s during attempt to convert to %s", actual, expected.getName()),
ex);
}
}
}

View File

@@ -1,233 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.bind;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.PropertyValues;
import org.springframework.util.Assert;
import org.springframework.validation.DataBinder;
import com.alibaba.otter.canal.client.adapter.config.common.CompositePropertySource;
import com.alibaba.otter.canal.client.adapter.config.common.EnumerablePropertySource;
import com.alibaba.otter.canal.client.adapter.config.common.PropertySource;
import com.alibaba.otter.canal.client.adapter.config.common.PropertySources;
/**
* A {@link PropertyValues} implementation backed by a {@link PropertySources},
* bridging the two abstractions and allowing (for instance) a regular
* {@link DataBinder} to be used with the latter.
*
* @author Dave Syer
* @author Phillip Webb
*/
public class PropertySourcesPropertyValues implements PropertyValues {
private static final Pattern COLLECTION_PROPERTY = Pattern
.compile("\\[(\\d+)\\](\\.\\S+)?");
private final PropertySources propertySources;
private final Collection<String> nonEnumerableFallbackNames;
private final PropertyNamePatternsMatcher includes;
private final Map<String, PropertyValue> propertyValues = new LinkedHashMap<>();
private final ConcurrentHashMap<String, PropertySource<?>> collectionOwners = new ConcurrentHashMap<>();
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

@@ -1,127 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.bind;
import java.util.EnumSet;
import java.util.Locale;
import java.util.Set;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.util.Assert;
/**
* Internal {@link ConversionService} used by {@link RelaxedDataBinder} to
* support additional relaxed conversion.
*
* @author Phillip Webb
* @author Stephane Nicoll
* @since 1.1.0
*/
class RelaxedConversionService implements ConversionService {
private final ConversionService conversionService;
private final GenericConversionService additionalConverters;
/**
* Create a new {@link RelaxedConversionService} instance.
*
* @param conversionService and option root conversion service
*/
RelaxedConversionService(ConversionService conversionService){
this.conversionService = conversionService;
this.additionalConverters = new GenericConversionService();
DefaultConversionService.addCollectionConverters(this.additionalConverters);
this.additionalConverters
.addConverterFactory(new RelaxedConversionService.StringToEnumIgnoringCaseConverterFactory());
this.additionalConverters.addConverter(new StringToCharArrayConverter());
}
@Override
public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
return (this.conversionService != null && this.conversionService.canConvert(sourceType, targetType))
|| this.additionalConverters.canConvert(sourceType, targetType);
}
@Override
public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {
return (this.conversionService != null && this.conversionService.canConvert(sourceType, targetType))
|| this.additionalConverters.canConvert(sourceType, targetType);
}
@Override
@SuppressWarnings("unchecked")
public <T> T convert(Object source, Class<T> targetType) {
Assert.notNull(targetType, "The targetType to convert to cannot be null");
return (T) convert(source, TypeDescriptor.forObject(source), TypeDescriptor.valueOf(targetType));
}
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (this.conversionService != null) {
try {
return this.conversionService.convert(source, sourceType, targetType);
} catch (ConversionFailedException ex) {
// Ignore and try the additional converters
}
}
return this.additionalConverters.convert(source, sourceType, targetType);
}
/**
* Clone of Spring's package private StringToEnumConverterFactory, but ignoring
* the case of the source.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private static class StringToEnumIgnoringCaseConverterFactory implements ConverterFactory<String, Enum> {
@Override
public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
Class<?> enumType = targetType;
while (enumType != null && !enumType.isEnum()) {
enumType = enumType.getSuperclass();
}
Assert.notNull(enumType, "The target type " + targetType.getName() + " does not refer to an enum");
return new RelaxedConversionService.StringToEnumIgnoringCaseConverterFactory.StringToEnum(enumType);
}
private static class StringToEnum<T extends Enum> implements Converter<String, T> {
private final Class<T> enumType;
StringToEnum(Class<T> enumType){
this.enumType = enumType;
}
@Override
public T convert(String source) {
if (source.isEmpty()) {
// It's an empty enum identifier: reset the enum value to null.
return null;
}
source = source.trim();
for (T candidate : (Set<T>) EnumSet.allOf(this.enumType)) {
RelaxedNames names = new RelaxedNames(
candidate.name().replace('_', '-').toLowerCase(Locale.ENGLISH));
for (String name : names) {
if (name.equals(source)) {
return candidate;
}
}
if (candidate.name().equalsIgnoreCase(source)) {
return candidate;
}
}
throw new IllegalArgumentException(
"No enum constant " + this.enumType.getCanonicalName() + "." + source);
}
}
}
}

View File

@@ -1,729 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.bind;
import java.beans.PropertyEditor;
import java.net.InetAddress;
import java.util.*;
import org.springframework.beans.*;
import org.springframework.beans.propertyeditors.FileEditor;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.validation.AbstractPropertyBindingResult;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.DataBinder;
/**
* Binder implementation that allows caller to bind to maps and also allows
* property names to match a bit loosely (if underscores or dashes are removed
* and replaced with camel case for example).
*
* @author Dave Syer
* @author Phillip Webb
* @author Stephane Nicoll
* @author Andy Wilkinson
* @see RelaxedNames
*/
public class RelaxedDataBinder extends DataBinder {
private static final Set<Class<?>> EXCLUDED_EDITORS;
static {
Set<Class<?>> excluded = new HashSet<>();
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<>();
/**
* 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<>(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<>();
Set<String> modifiedNames = new HashSet<>();
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<>();
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<>(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<>();
}
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<>();
}
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<>(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<>();
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<>();
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<>();
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<>();
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

@@ -1,241 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.bind;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.util.StringUtils;
/**
* Generates relaxed name variations from a given source.
*
* @author Phillip Webb
* @author Dave Syer
* @see RelaxedDataBinder
*/
public final class RelaxedNames implements Iterable<String> {
private static final Pattern CAMEL_CASE_PATTERN = Pattern.compile("([^A-Z-])([A-Z])");
private static final Pattern SEPARATED_TO_CAMEL_CASE_PATTERN = Pattern.compile("[_\\-.]");
private final String name;
private final Set<String> values = new LinkedHashSet<>();
/**
* 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

@@ -1,17 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.bind;
import org.springframework.core.convert.converter.Converter;
/**
* Converts a String to a Char Array.
*
* @author Phillip Webb
*/
class StringToCharArrayConverter implements Converter<String, char[]> {
@Override
public char[] convert(String source) {
return source.toCharArray();
}
}

View File

@@ -1,203 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.common;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import org.springframework.util.Assert;
import org.springframework.util.ResourceUtils;
/**
* Convenience base class for {@link Resource} implementations, pre-implementing
* typical behavior.
* <p>
* The "exists" method will check whether a File or InputStream can be opened;
* "isOpen" will always return false; "getURL" and "getFile" throw an exception;
* and "toString" will return the description.
*
* @author Juergen Hoeller
* @since 28.12.2003
*/
public abstract class AbstractResource implements Resource {
/**
* This implementation checks whether a File can be opened, falling back to
* whether an InputStream can be opened. This will cover both directories and
* content resources.
*/
@Override
public boolean exists() {
// Try file existence: can we find the file in the file system?
try {
return getFile().exists();
} catch (IOException ex) {
// Fall back to stream existence: can we open the stream?
try {
InputStream is = getInputStream();
is.close();
return true;
} catch (Throwable isEx) {
return false;
}
}
}
/**
* This implementation always returns {@code true}.
*/
@Override
public boolean isReadable() {
return true;
}
/**
* This implementation always returns {@code false}.
*/
@Override
public boolean isOpen() {
return false;
}
/**
* This implementation throws a FileNotFoundException, assuming that the
* resource cannot be resolved to a URL.
*/
@Override
public URL getURL() throws IOException {
throw new FileNotFoundException(getDescription() + " cannot be resolved to URL");
}
/**
* This implementation builds a URI based on the URL returned by
* {@link #getURL()}.
*/
@Override
public URI getURI() throws IOException {
URL url = getURL();
try {
return ResourceUtils.toURI(url);
} catch (URISyntaxException ex) {
throw new RuntimeException("Invalid URI [" + url + "]", ex);
}
}
/**
* This implementation throws a FileNotFoundException, assuming that the
* resource cannot be resolved to an absolute file path.
*/
@Override
public File getFile() throws IOException {
throw new FileNotFoundException(getDescription() + " cannot be resolved to absolute file path");
}
/**
* This implementation reads the entire InputStream to calculate the content
* length. Subclasses will almost always be able to provide a more optimal
* version of this, e.g. checking a File length.
*
* @see #getInputStream()
*/
@Override
public long contentLength() throws IOException {
InputStream is = getInputStream();
Assert.state(is != null, "Resource InputStream must not be null");
try {
long size = 0;
byte[] buf = new byte[255];
int read;
while ((read = is.read(buf)) != -1) {
size += read;
}
return size;
} finally {
try {
is.close();
} catch (IOException ex) {
}
}
}
/**
* This implementation checks the timestamp of the underlying File, if
* available.
*
* @see #getFileForLastModifiedCheck()
*/
@Override
public long lastModified() throws IOException {
long lastModified = getFileForLastModifiedCheck().lastModified();
if (lastModified == 0L) {
throw new FileNotFoundException(
getDescription() + " cannot be resolved in the file system for resolving its last-modified timestamp");
}
return lastModified;
}
/**
* Determine the File to use for timestamp checking.
* <p>
* The default implementation delegates to {@link #getFile()}.
*
* @return the File to use for timestamp checking (never {@code null})
* @throws FileNotFoundException if the resource cannot be resolved as an
* absolute file path, i.e. is not available in a file system
* @throws IOException in case of general resolution/reading failures
*/
protected File getFileForLastModifiedCheck() throws IOException {
return getFile();
}
/**
* This implementation throws a FileNotFoundException, assuming that relative
* resources cannot be created for this resource.
*/
@Override
public org.springframework.core.io.Resource createRelative(String relativePath) throws IOException {
throw new FileNotFoundException("Cannot create a relative resource for " + getDescription());
}
/**
* This implementation always returns {@code null}, assuming that this resource
* type does not have a filename.
*/
@Override
public String getFilename() {
return null;
}
/**
* This implementation returns the description of this resource.
*
* @see #getDescription()
*/
@Override
public String toString() {
return getDescription();
}
/**
* This implementation compares description strings.
*
* @see #getDescription()
*/
@Override
public boolean equals(Object obj) {
return (obj == this
|| (obj instanceof org.springframework.core.io.Resource
&& ((org.springframework.core.io.Resource) obj).getDescription().equals(getDescription())));
}
/**
* This implementation returns the description's hash code.
*
* @see #getDescription()
*/
@Override
public int hashCode() {
return getDescription().hashCode();
}
}

View File

@@ -1,117 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.common;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
/**
* {@link Resource} implementation for a given byte array.
* <p>
* Creates a {@link ByteArrayInputStream} for the given byte array.
* <p>
* Useful for loading content from any given byte array, without having to
* resort to a single-use {@link InputStreamResource}. Particularly useful for
* creating mail attachments from local content, where JavaMail needs to be able
* to read the stream multiple times.
*
* @author Juergen Hoeller
* @author Sam Brannen
* @see ByteArrayInputStream
* @see InputStreamResource
* @since 1.2.3
*/
public class ByteArrayResource extends AbstractResource {
private final byte[] byteArray;
private final String description;
/**
* Create a new {@code ByteArrayResource}.
*
* @param byteArray the byte array to wrap
*/
public ByteArrayResource(byte[] byteArray){
this(byteArray, "resource loaded from byte array");
}
/**
* Create a new {@code ByteArrayResource} with a description.
*
* @param byteArray the byte array to wrap
* @param description where the byte array comes from
*/
public ByteArrayResource(byte[] byteArray, String description){
Assert.notNull(byteArray, "Byte array must not be null");
this.byteArray = byteArray;
this.description = (description != null ? description : "");
}
/**
* Return the underlying byte array.
*/
public final byte[] getByteArray() {
return this.byteArray;
}
/**
* This implementation always returns {@code true}.
*/
@Override
public boolean exists() {
return true;
}
/**
* This implementation returns the length of the underlying byte array.
*/
@Override
public long contentLength() {
return this.byteArray.length;
}
/**
* This implementation returns a ByteArrayInputStream for the underlying byte
* array.
*
* @see ByteArrayInputStream
*/
@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(this.byteArray);
}
/**
* This implementation returns a description that includes the passed-in
* {@code description}, if any.
*/
@Override
public String getDescription() {
return "Byte array resource [" + this.description + "]";
}
/**
* This implementation compares the underlying byte array.
*
* @see Arrays#equals(byte[], byte[])
*/
@Override
public boolean equals(Object obj) {
return (obj == this || (obj instanceof org.springframework.core.io.ByteArrayResource
&& Arrays.equals(((ByteArrayResource) obj).byteArray, this.byteArray)));
}
/**
* This implementation returns the hash code based on the underlying byte array.
*/
@Override
public int hashCode() {
return (byte[].class.hashCode() * 29 * this.byteArray.length);
}
}

View File

@@ -1,107 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.common;
import java.util.*;
import org.springframework.util.StringUtils;
/**
* Composite {@link PropertySource} implementation that iterates over a set of
* {@link PropertySource} instances. Necessary in cases where multiple property
* sources share the same name, e.g. when multiple values are supplied to
* {@code @PropertySource}.
* <p>
* As of Spring 4.1.2, this class extends {@link EnumerablePropertySource}
* instead of plain {@link PropertySource}, exposing {@link #getPropertyNames()}
* based on the accumulated property names from all contained sources (as far as
* possible).
*
* @author Chris Beams
* @author Juergen Hoeller
* @author Phillip Webb
* @since 3.1.1
*/
public class CompositePropertySource extends EnumerablePropertySource<Object> {
private final Set<PropertySource<?>> propertySources = new LinkedHashSet<>();
/**
* 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<>();
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<>(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

@@ -1,58 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.common;
import org.springframework.util.ObjectUtils;
/**
* A {@link PropertySource} implementation capable of interrogating its
* underlying source object to enumerate all possible property name/value pairs.
* Exposes the {@link #getPropertyNames()} method to allow callers to introspect
* available properties without having to access the underlying source object.
* This also facilitates a more efficient implementation of
* {@link #containsProperty(String)}, in that it can call
* {@link #getPropertyNames()} and iterate through the returned array rather
* than attempting a call to {@link #getProperty(String)} which may be more
* expensive. Implementations may consider caching the result of
* {@link #getPropertyNames()} to fully exploit this performance opportunity.
* <p>
* Most framework-provided {@code PropertySource} implementations are
* enumerable; a counter-example would be {@code JndiPropertySource} where, due
* to the nature of JNDI it is not possible to determine all possible property
* names at any given time; rather it is only possible to try to access a
* property (via {@link #getProperty(String)}) in order to evaluate whether it
* is present or not.
*
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.1
*/
public abstract class EnumerablePropertySource<T> extends PropertySource<T> {
public EnumerablePropertySource(String name, T source){
super(name, source);
}
protected EnumerablePropertySource(String name){
super(name);
}
/**
* Return whether this {@code PropertySource} contains a property with the given
* name.
* <p>
* This implementation checks for the presence of the given name within the
* {@link #getPropertyNames()} array.
*
* @param name the name of the property to find
*/
@Override
public boolean containsProperty(String name) {
return ObjectUtils.containsElement(getPropertyNames(), name);
}
/**
* Return the names of all properties contained by the {@linkplain #getSource()
* source} object (never {@code null}).
*/
public abstract String[] getPropertyNames();
}

View File

@@ -1,38 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.common;
import java.util.Map;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.util.StringUtils;
/**
* {@link PropertySource} that reads keys and values from a {@code Map} object.
*
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.1
* @see PropertiesPropertySource
*/
public class MapPropertySource extends EnumerablePropertySource<Map<String, Object>> {
public MapPropertySource(String name, Map<String, Object> source){
super(name, source);
}
@Override
public Object getProperty(String name) {
return this.source.get(name);
}
@Override
public boolean containsProperty(String name) {
return this.source.containsKey(name);
}
@Override
public String[] getPropertyNames() {
return StringUtils.toStringArray(this.source.keySet());
}
}

View File

@@ -1,221 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.common;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.env.PropertyResolver;
import org.springframework.core.env.PropertySourcesPropertyResolver;
/**
* Default implementation of the {@link PropertySources} interface. Allows
* manipulation of contained property sources and provides a constructor for
* copying an existing {@code PropertySources} instance.
* <p>
* Where <em>precedence</em> is mentioned in methods such as {@link #addFirst}
* and {@link #addLast}, this is with regard to the order in which property
* sources will be searched when resolving a given property with a
* {@link PropertyResolver}.
*
* @author Chris Beams
* @author Juergen Hoeller
* @see PropertySourcesPropertyResolver
* @since 3.1
*/
public class MutablePropertySources implements PropertySources {
private final Log logger;
private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
/**
* 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

@@ -1,34 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.common;
import java.util.Map;
import java.util.Properties;
import org.springframework.core.env.PropertySource;
/**
* {@link PropertySource} implementation that extracts properties from a
* {@link Properties} object.
* <p>
* Note that because a {@code Properties} object is technically an
* {@code <Object, Object>} {@link java.util.Hashtable Hashtable}, one may
* contain non-{@code String} keys or values. This implementation, however is
* restricted to accessing only {@code String}-based keys and values, in the
* same fashion as {@link Properties#getProperty} and
* {@link Properties#setProperty}.
*
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.1
*/
public class PropertiesPropertySource extends MapPropertySource {
@SuppressWarnings({ "unchecked", "rawtypes" })
public PropertiesPropertySource(String name, Properties source){
super(name, (Map) source);
}
protected PropertiesPropertySource(String name, Map<String, Object> source){
super(name, source);
}
}

View File

@@ -1,239 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.common;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* Abstract base class representing a source of name/value property pairs. The
* underlying {@linkplain #getSource() source object} may be of any type
* {@code T} that encapsulates properties. Examples include
* {@link java.util.Properties} objects, {@link java.util.Map} objects,
* {@code ServletContext} and {@code ServletConfig} objects (for access to init
* parameters). Explore the {@code PropertySource} type hierarchy to see
* provided implementations.
* <p>
* {@code PropertySource} objects are not typically used in isolation, but
* rather through a {@link PropertySources} object, which aggregates property
* sources and in conjunction with a {@link PropertyResolver} implementation
* that can perform precedence-based searches across the set of
* {@code PropertySources}.
* <p>
* {@code PropertySource} identity is determined not based on the content of
* encapsulated properties, but rather based on the {@link #getName() name} of
* the {@code PropertySource} alone. This is useful for manipulating
* {@code PropertySource} objects when in collection contexts. See operations in
* {@link MutablePropertySources} as well as the {@link #named(String)} and
* {@link #toString()} methods for details.
* <p>
* Note that when working
* with @{@link org.springframework.context.annotation.Configuration
* Configuration} classes that the @{@link PropertySource PropertySource}
* annotation provides a convenient and declarative way of adding property
* sources to the enclosing {@code Environment}.
*
* @author Chris Beams
* @since 3.1
* @see PropertySources
* @see MutablePropertySources
* @see PropertySource
*/
public abstract class PropertySource<T> {
protected final Log logger = LogFactory.getLog(getClass());
protected final String name;
protected final T source;
/**
* Create a new {@code PropertySource} with the given name and source object.
*/
public PropertySource(String name, T source){
Assert.hasText(name, "Property source name must contain at least one character");
Assert.notNull(source, "Property source must not be null");
this.name = name;
this.source = source;
}
/**
* Create a new {@code PropertySource} with the given name and with a new
* {@code Object} instance as the underlying source.
* <p>
* Often useful in testing scenarios when creating anonymous implementations
* that never query an actual source but rather return hard-coded values.
*/
@SuppressWarnings("unchecked")
public PropertySource(String name){
this(name, (T) new Object());
}
/**
* Return the name of this {@code PropertySource}
*/
public String getName() {
return this.name;
}
/**
* Return the underlying source object for this {@code PropertySource}.
*/
public T getSource() {
return this.source;
}
/**
* Return whether this {@code PropertySource} contains the given name.
* <p>
* This implementation simply checks for a {@code null} return value from
* {@link #getProperty(String)}. Subclasses may wish to implement a more
* efficient algorithm if possible.
*
* @param name the property name to find
*/
public boolean containsProperty(String name) {
return (getProperty(name) != null);
}
/**
* Return the value associated with the given name, or {@code null} if not
* found.
*
* @param name the property to find
*/
public abstract Object getProperty(String name);
/**
* This {@code PropertySource} object is equal to the given object if:
* <ul>
* <li>they are the same instance
* <li>the {@code name} properties for both objects are equal
* </ul>
* <p>
* No properties other than {@code name} are evaluated.
*/
@Override
public boolean equals(Object obj) {
return (this == obj || (obj instanceof PropertySource
&& ObjectUtils.nullSafeEquals(this.name, ((PropertySource<?>) obj).name)));
}
/**
* Return a hash code derived from the {@code name} property of this
* {@code PropertySource} object.
*/
@Override
public int hashCode() {
return ObjectUtils.nullSafeHashCode(this.name);
}
/**
* Produce concise output (type and name) if the current log level does not
* include debug. If debug is enabled, produce verbose output including the hash
* code of the PropertySource instance and every name/value property pair.
* <p>
* This variable verbosity is useful as a property source such as system
* properties or environment variables may contain an arbitrary number of
* property pairs, potentially leading to difficult to read exception and log
* messages.
*
* @see Log#isDebugEnabled()
*/
@Override
public String toString() {
if (logger.isDebugEnabled()) {
return getClass().getSimpleName() + "@" + System.identityHashCode(this) + " {name='" + this.name
+ "', properties=" + this.source + "}";
} else {
return getClass().getSimpleName() + " {name='" + this.name + "'}";
}
}
/**
* Return a {@code PropertySource} implementation intended for collection
* comparison purposes only.
* <p>
* Primarily for internal use, but given a collection of {@code PropertySource}
* objects, may be used as follows:
*
* <pre class="code">
*
* {
* &#64;code
* List<PropertySource<?>> sources = new ArrayList<PropertySource<?>>();
* sources.add(new MapPropertySource("sourceA", mapA));
* sources.add(new MapPropertySource("sourceB", mapB));
* assert sources.contains(PropertySource.named("sourceA"));
* assert sources.contains(PropertySource.named("sourceB"));
* assert !sources.contains(PropertySource.named("sourceC"));
* }
* </pre>
*
* The returned {@code PropertySource} will throw
* {@code UnsupportedOperationException} if any methods other than
* {@code equals(Object)}, {@code hashCode()}, and {@code toString()} are
* called.
*
* @param name the name of the comparison {@code PropertySource} to be created
* and returned.
*/
public static PropertySource<?> named(String name) {
return new ComparisonPropertySource(name);
}
/**
* {@code PropertySource} to be used as a placeholder in cases where an actual
* property source cannot be eagerly initialized at application context creation
* time. For example, a {@code ServletContext}-based property source must wait
* until the {@code ServletContext} object is available to its enclosing
* {@code ApplicationContext}. In such cases, a stub should be used to hold the
* intended default position/order of the property source, then be replaced
* during context refresh.
*
* @see org.springframework.web.context.support.StandardServletEnvironment
* @see org.springframework.web.context.support.ServletContextPropertySource
*/
public static class StubPropertySource extends PropertySource<Object> {
public StubPropertySource(String name){
super(name, new Object());
}
/**
* Always returns {@code null}.
*/
@Override
public String getProperty(String name) {
return null;
}
}
/**
* @see PropertySource#named(String)
*/
static class ComparisonPropertySource extends StubPropertySource {
private static final String USAGE_ERROR = "ComparisonPropertySource instances are for use with collection comparison only";
public ComparisonPropertySource(String name){
super(name);
}
@Override
public Object getSource() {
throw new UnsupportedOperationException(USAGE_ERROR);
}
@Override
public boolean containsProperty(String name) {
throw new UnsupportedOperationException(USAGE_ERROR);
}
@Override
public String getProperty(String name) {
throw new UnsupportedOperationException(USAGE_ERROR);
}
}
}

View File

@@ -1,35 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.common;
import java.io.IOException;
import org.springframework.core.io.support.SpringFactoriesLoader;
/**
* Strategy interface located via {@link SpringFactoriesLoader} and used to load
* a {@link PropertySource}.
*
* @author Dave Syer
* @author Phillip Webb
*/
public interface PropertySourceLoader {
/**
* Returns the file extensions that the loader supports (excluding the '.').
*
* @return the file extensions
*/
String[] getFileExtensions();
/**
* Load the resource into a property source.
*
* @param name the name of the property source
* @param resource the resource to load
* @param profile the name of the profile to load or {@code null}. The profile
* can be used to load multi-document files (such as YAML). Simple property
* formats should {@code null} when asked to load a profile.
* @return a property source or {@code null}
* @throws IOException if the source cannot be loaded
*/
PropertySource<?> load(String name, Resource resource, String profile) throws IOException;
}

View File

@@ -1,25 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.common;
/**
* Holder containing one or more {@link PropertySource} objects.
*
* @author Chris Beams
* @since 3.1
*/
public interface PropertySources extends Iterable<PropertySource<?>> {
/**
* Return whether a property source with the given name is contained.
*
* @param name the {@linkplain PropertySource#getName() name of the property source} to find
*/
boolean contains(String name);
/**
* Return the property source with the given name, {@code null} if not found.
*
* @param name the {@linkplain PropertySource#getName() name of the property source} to find
*/
PropertySource<?> get(String name);
}

View File

@@ -1,57 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.common;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import org.springframework.core.io.*;
import org.springframework.core.io.ByteArrayResource;
/**
* Interface for a resource descriptor that abstracts from the actual type of
* underlying resource, such as a file or class path resource.
* <p>
* An InputStream can be opened for every resource if it exists in physical
* form, but a URL or File handle can just be returned for certain resources.
* The actual behavior is implementation-specific.
*
* @author Juergen Hoeller
* @since 28.12.2003
* @see #getInputStream()
* @see #getURL()
* @see #getURI()
* @see #getFile()
* @see WritableResource
* @see ContextResource
* @see UrlResource
* @see ClassPathResource
* @see FileSystemResource
* @see PathResource
* @see ByteArrayResource
* @see InputStreamResource
*/
public interface Resource extends InputStreamSource {
boolean exists();
boolean isReadable();
boolean isOpen();
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException;
long contentLength() throws IOException;
long lastModified() throws IOException;
org.springframework.core.io.Resource createRelative(String var1) throws IOException;
String getFilename();
String getDescription();
}

View File

@@ -1,182 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.common;
import java.util.*;
import org.springframework.core.env.Environment;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
* {@link YamlProcessor.DocumentMatcher} backed by
* {@link Environment#getActiveProfiles()}. A YAML document may define a
* "spring.profiles" element as a comma-separated list of Spring profile names,
* optionally negated using the {@code !} character. If both negated and
* non-negated profiles are specified for a single document, at least one
* non-negated profile must match and no negated profiles may match.
*
* @author Dave Syer
* @author Matt Benson
* @author Phillip Webb
* @author Andy Wilkinson
*/
public class SpringProfileDocumentMatcher implements YamlProcessor.DocumentMatcher {
private String[] activeProfiles = new String[0];
public SpringProfileDocumentMatcher(){
}
public SpringProfileDocumentMatcher(String... profiles){
addActiveProfiles(profiles);
}
public void addActiveProfiles(String... profiles) {
LinkedHashSet<String> set = new LinkedHashSet<>(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<>(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<>();
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<>();
public List<String> getProfiles() {
return this.profiles;
}
public void setProfiles(List<String> profiles) {
this.profiles = profiles;
}
}
}

View File

@@ -1,417 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.common;
import java.io.IOException;
import java.io.Reader;
import java.util.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.parser.ParserException;
import org.yaml.snakeyaml.reader.UnicodeReader;
/**
* Base class for YAML factories.
*
* @author Dave Syer
* @author Juergen Hoeller
* @since 4.1
*/
public abstract class YamlProcessor {
private final Log logger = LogFactory.getLog(getClass());
private ResolutionMethod resolutionMethod = ResolutionMethod.OVERRIDE;
private Resource[] resources = new Resource[0];
private List<DocumentMatcher> documentMatchers = Collections.emptyList();
private boolean matchDefault = true;
/**
* A map of document matchers allowing callers to selectively use only some of
* the documents in a YAML resource. In YAML documents are separated by
* <code>---<code> lines, and each document is converted to properties before
* the match is made. E.g.
*
* <pre class="code">
* environment: dev
* url: http://dev.bar.com
* name: Developer Setup
* ---
* environment: prod
* url:http://foo.bar.com
* name: My Cool App
* </pre>
*
* when mapped with
*
* <pre class="code">
* setDocumentMatchers(properties -> ("prod"
* .equals(properties.getProperty("environment")) ? MatchStatus.FOUND : MatchStatus.NOT_FOUND));
* </pre>
*
* would end up as
*
* <pre class="code">
* environment=prod
* url=http://foo.bar.com
* name=My Cool App
* </pre>
*/
public void setDocumentMatchers(DocumentMatcher... matchers) {
this.documentMatchers = Arrays.asList(matchers);
}
/**
* Flag indicating that a document for which all the
* {@link #setDocumentMatchers(DocumentMatcher...) document matchers} abstain
* will nevertheless match. Default is {@code true}.
*/
public void setMatchDefault(boolean matchDefault) {
this.matchDefault = matchDefault;
}
/**
* Method to use for resolving resources. Each resource will be converted to a
* Map, so this property is used to decide which map entries to keep in the
* final output from this factory. Default is {@link ResolutionMethod#OVERRIDE}.
*/
public void setResolutionMethod(ResolutionMethod resolutionMethod) {
Assert.notNull(resolutionMethod, "ResolutionMethod must not be null");
this.resolutionMethod = resolutionMethod;
}
/**
* Set locations of YAML {@link Resource resources} to be loaded.
*
* @see ResolutionMethod
*/
public void setResources(Resource... resources) {
this.resources = resources;
}
/**
* Provide an opportunity for subclasses to process the Yaml parsed from the
* supplied resources. Each resource is parsed in turn and the documents inside
* checked against the {@link #setDocumentMatchers(DocumentMatcher...)
* matchers}. If a document matches it is passed into the callback, along with
* its representation as Properties. Depending on the
* {@link #setResolutionMethod(ResolutionMethod)} not all of the documents will
* be parsed.
*
* @param callback a callback to delegate to once matching documents are found
* @see #createYaml()
*/
protected void process(MatchCallback callback) {
Yaml yaml = createYaml();
for (Resource resource : this.resources) {
boolean found = process(callback, yaml, resource);
if (this.resolutionMethod == ResolutionMethod.FIRST_FOUND && found) {
return;
}
}
}
/**
* Create the {@link Yaml} instance to use.
*/
protected Yaml createYaml() {
return new Yaml(new StrictMapAppenderConstructor());
}
private boolean process(MatchCallback callback, Yaml yaml, Resource resource) {
int count = 0;
try {
if (logger.isDebugEnabled()) {
logger.debug("Loading from YAML: " + resource);
}
try (Reader reader = new UnicodeReader(resource.getInputStream())) {
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);
}
}
} 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<>();
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<>();
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

@@ -1,80 +0,0 @@
package com.alibaba.otter.canal.client.adapter.config.common;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.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<>();
process((properties, map) -> result.putAll(getFlattenedMap(map)));
return result;
}
}
}

View File

@@ -0,0 +1,28 @@
package com.alibaba.otter.canal.client.adapter.support;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Created by @author zhuchao on @date 2021/11/11.
*/
public class FileName2KeyMapping {
private static Map<String, String> MAP = new ConcurrentHashMap<>();
public static void register(String type, String fileName, String key) {
MAP.putIfAbsent(join(type, fileName), key);
}
public static void unregister(String type, String fileName) {
MAP.remove(join(type, fileName));
}
public static String getKey(String type, String fileName) {
return MAP.get(join(type, fileName));
}
private static String join(String type, String fileName) {
return type + "|" + fileName;
}
}

View File

@@ -55,7 +55,7 @@ public class MappingConfigsLoader {
}
if (filePath.exists()) {
String fileName = filePath.getName();
if (!fileName.endsWith(".yml")) {
if (!(fileName.endsWith(".yml") || fileName.endsWith(".yaml"))) {
return null;
}
try (InputStream in = new FileInputStream(filePath)) {

View File

@@ -30,6 +30,8 @@ public class Util {
private static final Logger logger = LoggerFactory.getLogger(Util.class);
public static final String AUTO_GENERATED_PREFIX = "AUTO_GENERATED_";
/**
* 通过DS执行sql
*/

View File

@@ -0,0 +1,98 @@
package com.alibaba.otter.canal.client.adapter.support;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.lang.StringUtils;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.boot.origin.OriginTrackedValue;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
public class YamlUtils {
public static <T> T resourceYmlToObj(String resource, String prefix, Class<T> clazz) {
ClassPathResource classPathResource = new ClassPathResource(resource);
String content;
try (InputStream inputStream = classPathResource.getInputStream()) {
ByteArrayOutputStream result = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) != -1) {
result.write(buffer, 0, length);
}
content = result.toString("UTF-8");
} catch (IOException e) {
throw new RuntimeException(e);
}
return ymlToObj(prefix, content, clazz);
}
public static <T> T ymlToObj(String content, Class<T> clazz) {
return ymlToObj("", content, clazz, null, null);
}
public static <T> T ymlToObj(String prefix, String content, Class<T> clazz) {
return ymlToObj(prefix, content, clazz, null, null);
}
public static <T> T ymlToObj(String prefix, String content, Class<T> clazz, String charset) {
return ymlToObj(prefix, content, clazz, charset, null);
}
public static <T> T ymlToObj(String prefix, String content, Class<T> clazz, String charset,
Properties baseProperties) {
try {
prefix = StringUtils.trimToEmpty(prefix);
byte[] contentBytes;
if (charset == null) {
contentBytes = content.getBytes(StandardCharsets.UTF_8);
} else {
contentBytes = content.getBytes(charset);
}
YamlPropertySourceLoader propertySourceLoader = new YamlPropertySourceLoader();
Resource configResource = new ByteArrayResource(contentBytes);
List<PropertySource<?>> propertySources = propertySourceLoader.load("manualBindConfig", configResource);
if (propertySources == null || propertySources.isEmpty()) {
return null;
}
PropertySource<?> propertySource = propertySources.get(0);
Properties properties = new Properties();
properties.putAll((Map<?, ?>) propertySource.getSource());
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
Object value = entry.getValue();
if (value instanceof OriginTrackedValue) {
entry.setValue(((OriginTrackedValue) value).getValue());
}
}
if (baseProperties != null) {
baseProperties.putAll(properties);
properties = baseProperties;
}
ConfigurationPropertySource sources = new MapConfigurationPropertySource(properties);
Binder binder = new Binder(sources);
return binder.bind(prefix, Bindable.of(clazz)).get();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,229 @@
package com.alibaba.otter.canal.client.adapter.support;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import org.junit.Assert;
import org.junit.Test;
public class YamlUtilsTest {
@Test
public void testLoadConfigToYml() {
String configStr="dataSourceKey: defaultDS\n"
+ "destination: example\n"
+ "groupId: g1\n"
+ "outerAdapterKey: mysql1\n"
+ "concurrent: true\n"
+ "dbMapping:\n"
+ " database: mytest\n"
+ " table: user\n"
+ " targetTable: mytest2.user\n"
+ " targetPk:\n"
+ " id: id\n"
+ "# mapAll: true\n"
+ " targetColumns:\n"
+ " id:\n"
+ " name:\n"
+ " role_id:\n"
+ " c_time:\n"
+ " test1:\n"
+ " etlCondition: \"where c_time>={}\"\n"
+ " commitBatch: 3000 # 批量提交的大小";
MappingConfig config = YamlUtils.ymlToObj(null, configStr, MappingConfig.class, null, new Properties());
Assert.assertNotNull(config);
Assert.assertEquals(config.getDestination(), "example");
Assert.assertEquals(config.getOuterAdapterKey(), "mysql1");
Assert.assertEquals(config.getDbMapping().getDatabase(), "mytest");
Assert.assertEquals(config.getDbMapping().getTargetColumns().size(), 5);
}
private static class MappingConfig {
private String dataSourceKey;
private String destination;
private String groupId;
private String outerAdapterKey;
private boolean concurrent = false;
private DbMapping dbMapping;
public String getDataSourceKey() {
return dataSourceKey;
}
public void setDataSourceKey(String dataSourceKey) {
this.dataSourceKey = dataSourceKey;
}
public String getDestination() {
return destination;
}
public void setDestination(String destination) {
this.destination = destination;
}
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public String getOuterAdapterKey() {
return outerAdapterKey;
}
public void setOuterAdapterKey(String outerAdapterKey) {
this.outerAdapterKey = outerAdapterKey;
}
public boolean isConcurrent() {
return concurrent;
}
public void setConcurrent(boolean concurrent) {
this.concurrent = concurrent;
}
public DbMapping getDbMapping() {
return dbMapping;
}
public void setDbMapping(DbMapping dbMapping) {
this.dbMapping = dbMapping;
}
}
private static class DbMapping {
private boolean mirrorDb = false; // 是否镜像库
private String database; // 数据库名或schema名
private String table; // 表名
private Map<String, String> targetPk = new LinkedHashMap<>(); // 目标表主键字段
private boolean mapAll = false; // 映射所有字段
private String targetDb; // 目标库名
private String targetTable; // 目标表名
private Map<String, String> targetColumns; // 目标表字段映射
private boolean caseInsensitive = false; // 目标表不区分大小写,默认是否
private String etlCondition; // etl条件sql
private int readBatch = 5000;
private int commitBatch = 5000; // etl等批量提交大小
private Map<String, String> allMapColumns;
public boolean isMirrorDb() {
return mirrorDb;
}
public void setMirrorDb(boolean mirrorDb) {
this.mirrorDb = mirrorDb;
}
public String getDatabase() {
return database;
}
public void setDatabase(String database) {
this.database = database;
}
public String getTable() {
return table;
}
public void setTable(String table) {
this.table = table;
}
public Map<String, String> getTargetPk() {
return targetPk;
}
public void setTargetPk(Map<String, String> targetPk) {
this.targetPk = targetPk;
}
public boolean isMapAll() {
return mapAll;
}
public void setMapAll(boolean mapAll) {
this.mapAll = mapAll;
}
public String getTargetDb() {
return targetDb;
}
public void setTargetDb(String targetDb) {
this.targetDb = targetDb;
}
public String getTargetTable() {
return targetTable;
}
public void setTargetTable(String targetTable) {
this.targetTable = targetTable;
}
public Map<String, String> getTargetColumns() {
return targetColumns;
}
public void setTargetColumns(Map<String, String> targetColumns) {
this.targetColumns = targetColumns;
}
public boolean isCaseInsensitive() {
return caseInsensitive;
}
public void setCaseInsensitive(boolean caseInsensitive) {
this.caseInsensitive = caseInsensitive;
}
public String getEtlCondition() {
return etlCondition;
}
public void setEtlCondition(String etlCondition) {
this.etlCondition = etlCondition;
}
public int getReadBatch() {
return readBatch;
}
public void setReadBatch(int readBatch) {
this.readBatch = readBatch;
}
public int getCommitBatch() {
return commitBatch;
}
public void setCommitBatch(int commitBatch) {
this.commitBatch = commitBatch;
}
public Map<String, String> getAllMapColumns() {
return allMapColumns;
}
public void setAllMapColumns(Map<String, String> allMapColumns) {
this.allMapColumns = allMapColumns;
}
}
}

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.6-SNAPSHOT</version>
<version>1.1.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -27,22 +27,22 @@
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>6.4.3</version>
<version>6.8.22</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>transport</artifactId>
<version>6.4.3</version>
<version>6.8.22</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>6.4.3</version>
<version>6.8.22</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>6.4.3</version>
<version>6.8.22</version>
</dependency>
<dependency>

View File

@@ -316,13 +316,17 @@ public class ES6xTemplate implements ESTemplate {
}
@Override
public Object getESDataFromDmlData(ESSyncConfig.ESMapping mapping, Map<String, Object> dmlData,
public Object getESDataFromDmlData(ESSyncConfig.ESMapping mapping,String owner, 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();
ColumnItem columnItem = fieldItem.getColumnItems().iterator().next();
if (!columnItem.getOwner().equals(owner)) {
continue;
}
String columnName = columnItem.getColumnName();
if (fieldItem.getFieldName().equals(idFieldName)) {
resultIdVal = getValFromData(mapping, dmlData, fieldItem.getFieldName(), columnName);

View File

@@ -150,7 +150,13 @@ public class ESConnection {
logger.error(e.getMessage(), e);
return null;
}
mappingMetaData = mappings.get(index).get(type);
//通过别名查询mapping返回的是真实索引名称,mappings.get(index)返回null,为兼容别名情况修改如下:
ImmutableOpenMap<String, MappingMetaData> esIndex = mappings.get(index);
if(esIndex == null){
esIndex = mappings.valuesIt().next();
}
mappingMetaData = esIndex.get(type);
}
return mappingMetaData;
}

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.6-SNAPSHOT</version>
<version>1.1.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -287,8 +287,7 @@ public class ES7xTemplate implements ESTemplate {
}
@Override
public Object getESDataFromDmlData(ESMapping mapping, Map<String, Object> dmlData,
Map<String, Object> esFieldData) {
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;
@@ -312,13 +311,17 @@ public class ES7xTemplate implements ESTemplate {
}
@Override
public Object getESDataFromDmlData(ESMapping mapping, Map<String, Object> dmlData, Map<String, Object> dmlOld,
public Object getESDataFromDmlData(ESMapping mapping,String owner, 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();
ColumnItem columnItem = fieldItem.getColumnItems().iterator().next();
if (!columnItem.getOwner().equals(owner)) {
continue;
}
String columnName = columnItem.getColumnName();
if (fieldItem.getFieldName().equals(idFieldName)) {
resultIdVal = getValFromData(mapping, dmlData, fieldItem.getFieldName(), columnName);

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.6-SNAPSHOT</version>
<version>1.1.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -22,6 +22,7 @@
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<scope>provided</scope>
</dependency>
<dependency>

View File

@@ -18,10 +18,7 @@ import com.alibaba.otter.canal.client.adapter.es.core.config.SqlParser;
import com.alibaba.otter.canal.client.adapter.es.core.monitor.ESConfigMonitor;
import com.alibaba.otter.canal.client.adapter.es.core.service.ESSyncService;
import com.alibaba.otter.canal.client.adapter.es.core.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.*;
/**
* ES外部适配器
@@ -42,6 +39,8 @@ public abstract class ESAdapter implements OuterAdapter {
protected Properties envProperties;
protected OuterAdapterConfig configuration;
public ESSyncService getEsSyncService() {
return esSyncService;
}
@@ -58,23 +57,13 @@ public abstract class ESAdapter implements OuterAdapter {
public void init(OuterAdapterConfig configuration, Properties envProperties) {
try {
this.envProperties = envProperties;
this.configuration = configuration;
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);
}
addConfig(key, config);
});
for (Map.Entry<String, ESSyncConfig> entry : esSyncConfig.entrySet()) {
String configName = entry.getKey();
ESSyncConfig config = entry.getValue();
addSyncConfigToCache(configName, config);
}
esSyncService = new ESSyncService(esTemplate);
esConfigMonitor = new ESConfigMonitor();
@@ -103,12 +92,12 @@ public abstract class ESAdapter implements OuterAdapter {
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);
configMap = dbTableEsSyncConfig
.get(StringUtils.trimToEmpty(dml.getDestination()) + "-" + StringUtils.trimToEmpty(dml.getGroupId())
+ "_" + database + "-" + table);
} else {
configMap = dbTableEsSyncConfig.get(StringUtils.trimToEmpty(dml.getDestination()) + "_" + database + "-"
+ table);
configMap = dbTableEsSyncConfig
.get(StringUtils.trimToEmpty(dml.getDestination()) + "_" + database + "-" + table);
}
if (configMap != null && !configMap.values().isEmpty()) {
@@ -138,7 +127,7 @@ public abstract class ESAdapter implements OuterAdapter {
return null;
}
public void addSyncConfigToCache(String configName, ESSyncConfig config) {
private void addSyncConfigToCache(String configName, ESSyncConfig config) {
Properties envProperties = this.envProperties;
SchemaItem schemaItem = SqlParser.parse(config.getEsMapping().getSql());
config.getEsMapping().setSchemaItem(schemaItem);
@@ -153,30 +142,60 @@ public abstract class ESAdapter implements OuterAdapter {
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(),
schemaItem.getAliasTableItems().values().forEach(tableItem -> {
Map<String, ESSyncConfig> esSyncConfigMap;
String schemaKey = tableItem.getSchema() == null ? schema : tableItem.getSchema();
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
esSyncConfigMap = dbTableEsSyncConfig
.computeIfAbsent(StringUtils.trimToEmpty(config.getDestination()) + "-"
+ StringUtils.trimToEmpty(config.getGroupId()) + "_" + schemaKey + "-"
+ tableItem.getTableName(),
k -> new ConcurrentHashMap<>());
} else {
esSyncConfigMap = dbTableEsSyncConfig.computeIfAbsent(StringUtils.trimToEmpty(config.getDestination())
+ "_"
+ schema
+ "-"
+ tableItem.getTableName(),
k -> new ConcurrentHashMap<>());
}
} else {
esSyncConfigMap = dbTableEsSyncConfig.computeIfAbsent(
StringUtils.trimToEmpty(config.getDestination()) + "_" + schemaKey + "-" + tableItem.getTableName(),
k -> new ConcurrentHashMap<>());
}
esSyncConfigMap.put(configName, config);
});
esSyncConfigMap.put(configName, config);
});
}
public boolean addConfig(String fileName, ESSyncConfig config) {
if (match(config)) {
esSyncConfig.put(fileName, config);
addSyncConfigToCache(fileName, config);
FileName2KeyMapping.register(getClass().getAnnotation(SPI.class).value(), fileName, configuration.getKey());
return true;
}
return false;
}
public void updateConfig(String fileName, ESSyncConfig config) {
if (config.getOuterAdapterKey() != null && !config.getOuterAdapterKey().equals(configuration.getKey())) {
// 理论上不允许改这个 因为本身就是通过这个关联起Adapter和Config的
throw new RuntimeException("not allow to change outAdapterKey");
}
esSyncConfig.put(fileName, config);
addSyncConfigToCache(fileName, config);
}
public void deleteConfig(String fileName) {
esSyncConfig.remove(fileName);
for (Map<String, ESSyncConfig> configMap : dbTableEsSyncConfig.values()) {
if (configMap != null) {
configMap.remove(fileName);
}
}
FileName2KeyMapping.unregister(getClass().getAnnotation(SPI.class).value(), fileName);
}
private boolean match(ESSyncConfig config) {
boolean sameMatch = config.getOuterAdapterKey() != null
&& config.getOuterAdapterKey().equalsIgnoreCase(configuration.getKey());
boolean prefixMatch = config.getOuterAdapterKey() == null && configuration.getKey()
.startsWith(StringUtils
.join(new String[] { Util.AUTO_GENERATED_PREFIX, config.getDestination(), config.getGroupId() }, '-'));
return sameMatch || prefixMatch;
}
}

View File

@@ -1,5 +1,6 @@
package com.alibaba.otter.canal.client.adapter.es.core.config;
import com.alibaba.otter.canal.client.adapter.support.YamlUtils;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
@@ -7,7 +8,6 @@ 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;
/**
@@ -28,7 +28,7 @@ public class ESSyncConfigLoader {
String esv = envProperties.getProperty("es.version");
Map<String, String> configContentMap = MappingConfigsLoader.loadConfigs(esv);
configContentMap.forEach((fileName, content) -> {
ESSyncConfig config = YmlConfigBinder.bindYmlToObj(null, content, ESSyncConfig.class, null, envProperties);
ESSyncConfig config = YamlUtils.ymlToObj(null, content, ESSyncConfig.class, null, envProperties);
if (config == null) {
return;
}

View File

@@ -1,7 +1,6 @@
package com.alibaba.otter.canal.client.adapter.es.core.monitor;
import java.io.File;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.io.filefilter.FileFilterUtils;
@@ -11,11 +10,13 @@ import org.apache.commons.io.monitor.FileAlterationObserver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.client.adapter.config.YmlConfigBinder;
import com.alibaba.otter.canal.client.adapter.es.core.ESAdapter;
import com.alibaba.otter.canal.client.adapter.es.core.config.ESSyncConfig;
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
import com.alibaba.otter.canal.client.adapter.support.Util;
import com.alibaba.otter.canal.client.adapter.support.YamlUtils;
;
public class ESConfigMonitor {
@@ -36,7 +37,7 @@ public class ESConfigMonitor {
File confDir = Util.getConfDirPath(adapterName);
try {
FileAlterationObserver observer = new FileAlterationObserver(confDir,
FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml")));
FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml")));
FileListener listener = new FileListener();
observer.addListener(listener);
fileMonitor = new FileAlterationMonitor(3000, observer);
@@ -63,15 +64,20 @@ public class ESConfigMonitor {
try {
// 加载新增的配置文件
String configContent = MappingConfigsLoader.loadConfig(adapterName + File.separator + file.getName());
ESSyncConfig config = YmlConfigBinder.bindYmlToObj(null,
ESSyncConfig config = YamlUtils.ymlToObj(null,
configContent,
ESSyncConfig.class,
null,
envProperties);
if (config != null) {
// 这里要记得设置esVersion bugfix
config.setEsVersion(adapterName);
config.validate();
addConfigToCache(file, config);
logger.info("Add a new es mapping config: {} to canal adapter", file.getName());
boolean result = esAdapter.addConfig(file.getName(), config);
if (result) {
logger.info("Add a new es mapping config: {} to canal adapter",
file.getName());
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
@@ -91,7 +97,7 @@ public class ESConfigMonitor {
onFileDelete(file);
return;
}
ESSyncConfig config = YmlConfigBinder.bindYmlToObj(null,
ESSyncConfig config = YamlUtils.ymlToObj(null,
configContent,
ESSyncConfig.class,
null,
@@ -99,12 +105,10 @@ public class ESConfigMonitor {
if (config == null) {
return;
}
// 这里要记得设置esVersion bugfix
config.setEsVersion(adapterName);
config.validate();
if (esAdapter.getEsSyncConfig().containsKey(file.getName())) {
deleteConfigFromCache(file);
}
addConfigToCache(file, config);
esAdapter.updateConfig(file.getName(), config);
logger.info("Change a es mapping config: {} of canal adapter", file.getName());
}
} catch (Exception e) {
@@ -118,29 +122,12 @@ public class ESConfigMonitor {
try {
if (esAdapter.getEsSyncConfig().containsKey(file.getName())) {
deleteConfigFromCache(file);
esAdapter.deleteConfig(file.getName());
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);
esAdapter.addSyncConfigToCache(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

@@ -1,19 +1,15 @@
package com.alibaba.otter.canal.client.adapter.es.core.service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter;
import com.alibaba.otter.canal.client.adapter.es.core.config.ESSyncConfig;
import com.alibaba.otter.canal.client.adapter.es.core.config.ESSyncConfig.ESMapping;
import com.alibaba.otter.canal.client.adapter.es.core.config.SchemaItem;
@@ -79,7 +75,7 @@ public class ESSyncService {
esSyncConfigs
.forEach(esSyncConfig -> configIndexes.append(esSyncConfig.getEsMapping().get_index()).append(" "));
logger.debug("DML: {} \nAffected indexes: {}",
JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue),
JSON.toJSONString(dml, JSONWriter.Feature.WriteNulls),
configIndexes.toString());
}
}
@@ -209,7 +205,7 @@ public class ESSyncService {
if (schemaItem.getAliasTableItems().size() == 1 && schemaItem.isAllFieldsSimple()) {
// ------单表 & 所有字段都为简单字段------
singleTableSimpleFiledUpdate(config, dml, data, old);
singleTableSimpleFiledUpdate(config, schemaItem.getMainTable().getAlias(), dml, data, old);
} else {
// ------主表 查询sql来更新------
if (schemaItem.getMainTable().getTableName().equalsIgnoreCase(dml.getTable())) {
@@ -263,7 +259,7 @@ public class ESSyncService {
// 判断主键和所更新的字段是否全为简单字段
if (idFieldSimple && allUpdateFieldSimple && !fkChanged) {
singleTableSimpleFiledUpdate(config, dml, data, old);
singleTableSimpleFiledUpdate(config, schemaItem.getMainTable().getAlias(), dml, data, old);
} else {
mainTableUpdate(config, dml, data, old);
}
@@ -810,12 +806,12 @@ public class ESSyncService {
* @param data 单行data数据
* @param old 单行old数据
*/
private void singleTableSimpleFiledUpdate(ESSyncConfig config, Dml dml, Map<String, Object> data,
private void singleTableSimpleFiledUpdate(ESSyncConfig config, String owner, 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);
Object idVal = esTemplate.getESDataFromDmlData(mapping, owner, data, old, esFieldData);
if (logger.isTraceEnabled()) {
logger.trace("Main table update to es index, destination:{}, table: {}, index: {}, id: {}",

View File

@@ -18,7 +18,7 @@ import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson2.JSON;
import com.alibaba.otter.canal.client.adapter.es.core.config.ESSyncConfig.ESMapping;
import com.alibaba.otter.canal.client.adapter.es.core.config.SchemaItem;
import com.alibaba.otter.canal.client.adapter.es.core.config.SchemaItem.ColumnItem;

View File

@@ -57,13 +57,11 @@ public interface ESTemplate {
Object getIdValFromRS(ESMapping mapping, ResultSet resultSet) throws SQLException;
Object getESDataFromRS(ESMapping mapping, ResultSet resultSet, Map<String, Object> dmlOld,
Map<String, Object> esFieldData) throws SQLException;
Object getESDataFromRS(ESMapping mapping, ResultSet resultSet, Map<String, Object> dmlOld, Map<String, Object> esFieldData) throws SQLException;
Object getValFromData(ESMapping mapping, Map<String, Object> dmlData, String fieldName, String columnName);
Object getESDataFromDmlData(ESMapping mapping, Map<String, Object> dmlData, Map<String, Object> esFieldData);
Object getESDataFromDmlData(ESMapping mapping, Map<String, Object> dmlData, Map<String, Object> dmlOld,
Map<String, Object> esFieldData);
Object getESDataFromDmlData(ESMapping mapping,String owner, Map<String, Object> dmlData, Map<String, Object> dmlOld, Map<String, Object> esFieldData);
}

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.6-SNAPSHOT</version>
<version>1.1.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -1,9 +1,25 @@
package com.alibaba.otter.canal.client.adapter.hbase;
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfigLoader;
import com.alibaba.otter.canal.client.adapter.hbase.monitor.HbaseConfigMonitor;
import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseEtlService;
import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseSyncService;
import com.alibaba.otter.canal.client.adapter.hbase.support.HbaseTemplate;
import com.alibaba.otter.canal.client.adapter.support.Dml;
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
import com.alibaba.otter.canal.client.adapter.support.FileName2KeyMapping;
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
import com.alibaba.otter.canal.client.adapter.support.SPI;
import com.alibaba.otter.canal.client.adapter.support.Util;
import java.io.IOException;
import java.util.*;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
@@ -16,18 +32,6 @@ import org.apache.hadoop.hbase.filter.FirstKeyOnlyFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfigLoader;
import com.alibaba.otter.canal.client.adapter.hbase.monitor.HbaseConfigMonitor;
import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseEtlService;
import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseSyncService;
import com.alibaba.otter.canal.client.adapter.hbase.support.HbaseTemplate;
import com.alibaba.otter.canal.client.adapter.support.Dml;
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
import com.alibaba.otter.canal.client.adapter.support.SPI;
/**
* HBase外部适配器
*
@@ -49,6 +53,8 @@ public class HbaseAdapter implements OuterAdapter {
private Properties envProperties;
private OuterAdapterConfig configuration;
public Map<String, MappingConfig> getHbaseMapping() {
return hbaseMapping;
}
@@ -61,33 +67,12 @@ public class HbaseAdapter implements OuterAdapter {
public void init(OuterAdapterConfig configuration, Properties envProperties) {
try {
this.envProperties = envProperties;
this.configuration = configuration;
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);
}
hbaseMappingTmp.forEach((key, config) -> {
addConfig(key, config);
});
for (Map.Entry<String, MappingConfig> entry : hbaseMapping.entrySet()) {
String configName = entry.getKey();
MappingConfig mappingConfig = entry.getValue();
String k;
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
k = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "-"
+ StringUtils.trimToEmpty(mappingConfig.getGroupId()) + "_"
+ mappingConfig.getHbaseMapping().getDatabase() + "-"
+ mappingConfig.getHbaseMapping().getTable();
} else {
k = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_"
+ mappingConfig.getHbaseMapping().getDatabase() + "-"
+ mappingConfig.getHbaseMapping().getTable();
}
Map<String, MappingConfig> configMap = mappingConfigCache.computeIfAbsent(k,
k1 -> new ConcurrentHashMap<>());
configMap.put(configName, mappingConfig);
}
Map<String, String> properties = configuration.getProperties();
@@ -223,4 +208,62 @@ public class HbaseAdapter implements OuterAdapter {
}
return null;
}
private void addSyncConfigToCache(String configName, MappingConfig mappingConfig) {
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);
}
public boolean addConfig(String fileName, MappingConfig config) {
if (match(config)) {
hbaseMapping.put(fileName, config);
addSyncConfigToCache(fileName, config);
FileName2KeyMapping.register(getClass().getAnnotation(SPI.class).value(), fileName,
configuration.getKey());
return true;
}
return false;
}
public void updateConfig(String fileName, MappingConfig config) {
if (config.getOuterAdapterKey() != null && !config.getOuterAdapterKey()
.equals(configuration.getKey())) {
// 理论上不允许改这个 因为本身就是通过这个关联起Adapter和Config的
throw new RuntimeException("not allow to change outAdapterKey");
}
hbaseMapping.put(fileName, config);
addSyncConfigToCache(fileName, config);
}
public void deleteConfig(String fileName) {
hbaseMapping.remove(fileName);
for (Map<String, MappingConfig> configMap : mappingConfigCache.values()) {
if (configMap != null) {
configMap.remove(fileName);
}
}
FileName2KeyMapping.unregister(getClass().getAnnotation(SPI.class).value(), fileName);
}
private boolean match(MappingConfig config) {
boolean sameMatch = config.getOuterAdapterKey() != null && config.getOuterAdapterKey()
.equalsIgnoreCase(configuration.getKey());
boolean prefixMatch = config.getOuterAdapterKey() == null && configuration.getKey()
.startsWith(StringUtils
.join(new String[]{Util.AUTO_GENERATED_PREFIX, config.getDestination(),
config.getGroupId()}, '-'));
return sameMatch || prefixMatch;
}
}

View File

@@ -1,5 +1,6 @@
package com.alibaba.otter.canal.client.adapter.hbase.config;
import com.alibaba.otter.canal.client.adapter.support.YamlUtils;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
@@ -7,7 +8,6 @@ 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;
/**
@@ -32,8 +32,7 @@ public class MappingConfigLoader {
Map<String, String> configContentMap = MappingConfigsLoader.loadConfigs("hbase");
configContentMap.forEach((fileName, content) -> {
MappingConfig config = YmlConfigBinder
.bindYmlToObj(null, content, MappingConfig.class, null, envProperties);
MappingConfig config = YamlUtils.ymlToObj(null, content, MappingConfig.class, null, envProperties);
if (config == null) {
return;
}

View File

@@ -1,24 +1,19 @@
package com.alibaba.otter.canal.client.adapter.hbase.monitor;
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;
import com.alibaba.otter.canal.client.adapter.support.YamlUtils;
import java.io.File;
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 java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class HbaseConfigMonitor {
private static final Logger logger = LoggerFactory.getLogger(HbaseConfigMonitor.class);
@@ -37,7 +32,7 @@ public class HbaseConfigMonitor {
File confDir = Util.getConfDirPath(adapterName);
try {
FileAlterationObserver observer = new FileAlterationObserver(confDir,
FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml")));
FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml")));
FileListener listener = new FileListener();
observer.addListener(listener);
fileMonitor = new FileAlterationMonitor(3000, observer);
@@ -64,15 +59,16 @@ public class HbaseConfigMonitor {
try {
// 加载新增的配置文件
String configContent = MappingConfigsLoader.loadConfig(adapterName + File.separator + file.getName());
MappingConfig config = YmlConfigBinder
.bindYmlToObj(null, configContent, MappingConfig.class, null, envProperties);
MappingConfig config = YamlUtils.ymlToObj(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());
boolean result = hbaseAdapter.addConfig(file.getName(), config);
if (result) {
logger.info("Add a new hbase mapping config: {} to canal adapter",
file.getName());
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
@@ -91,16 +87,12 @@ public class HbaseConfigMonitor {
onFileDelete(file);
return;
}
MappingConfig config = YmlConfigBinder
.bindYmlToObj(null, configContent, MappingConfig.class, null, envProperties);
MappingConfig config = YamlUtils.ymlToObj(null, configContent, MappingConfig.class, null, envProperties);
if (config == null) {
return;
}
config.validate();
if (hbaseAdapter.getHbaseMapping().containsKey(file.getName())) {
deleteConfigFromCache(file);
}
addConfigToCache(file, config);
hbaseAdapter.updateConfig(file.getName(), config);
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
@@ -113,33 +105,12 @@ public class HbaseConfigMonitor {
try {
if (hbaseAdapter.getHbaseMapping().containsKey(file.getName())) {
deleteConfigFromCache(file);
hbaseAdapter.deleteConfig(file.getName());
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

@@ -6,8 +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.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter.Feature;
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;
@@ -39,7 +39,7 @@ public class HbaseSyncService {
delete(config, dml);
}
if (logger.isDebugEnabled()) {
logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
logger.debug("DML: {}", JSON.toJSONString(dml, Feature.WriteNulls));
}
}
}

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.6-SNAPSHOT</version>
<version>1.1.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -1,16 +1,5 @@
package com.alibaba.otter.canal.client.adapter.kudu;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
import com.alibaba.otter.canal.client.adapter.kudu.config.KuduMappingConfig;
import com.alibaba.otter.canal.client.adapter.kudu.config.KuduMappingConfigLoader;
@@ -20,8 +9,19 @@ import com.alibaba.otter.canal.client.adapter.kudu.service.KuduSyncService;
import com.alibaba.otter.canal.client.adapter.kudu.support.KuduTemplate;
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.FileName2KeyMapping;
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
import com.alibaba.otter.canal.client.adapter.support.SPI;
import com.alibaba.otter.canal.client.adapter.support.Util;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author liuyadong
@@ -35,8 +35,6 @@ public class KuduAdapter implements OuterAdapter {
private Map<String, KuduMappingConfig> kuduMapping = new ConcurrentHashMap<>(); // 文件名对应配置
private Map<String, Map<String, KuduMappingConfig>> mappingConfigCache = new ConcurrentHashMap<>(); // 库名-表名对应配置
private String dataSourceKey;
private KuduTemplate kuduTemplate;
private KuduSyncService kuduSyncService;
@@ -45,6 +43,8 @@ public class KuduAdapter implements OuterAdapter {
private Properties envProperties;
private OuterAdapterConfig configuration;
public Map<String, KuduMappingConfig> getKuduMapping() {
return kuduMapping;
}
@@ -56,36 +56,17 @@ public class KuduAdapter implements OuterAdapter {
@Override
public void init(OuterAdapterConfig configuration, Properties envProperties) {
this.envProperties = envProperties;
this.configuration = configuration;
Map<String, KuduMappingConfig> kuduMappingTmp = KuduMappingConfigLoader.load(envProperties);
// 过滤不匹配的key的配置,获取连接key,key为配置文件名称
kuduMappingTmp.forEach((key, mappingConfig) -> {
if ((mappingConfig.getOuterAdapterKey() == null && configuration.getKey() == null)
|| (mappingConfig.getOuterAdapterKey() != null && mappingConfig.getOuterAdapterKey()
.equalsIgnoreCase(configuration.getKey()))) {
kuduMapping.put(key, mappingConfig);
dataSourceKey = mappingConfig.getDataSourceKey();
}
kuduMappingTmp.forEach((key, config) -> {
addConfig(key, config);
});
// 判断目标字段是否为空
if (kuduMapping.isEmpty()) {
throw new RuntimeException("No kudu adapter found for config key: " + configuration.getKey());
}
for (Map.Entry<String, KuduMappingConfig> entry : kuduMapping.entrySet()) {
String configName = entry.getKey();
KuduMappingConfig 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.getKuduMapping().getDatabase() + "-" + mappingConfig.getKuduMapping().getTable();
} else {
k = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_"
+ mappingConfig.getKuduMapping().getDatabase() + "-" + mappingConfig.getKuduMapping().getTable();
}
Map<String, KuduMappingConfig> configMap = mappingConfigCache.computeIfAbsent(k,
k1 -> new ConcurrentHashMap<>());
configMap.put(configName, mappingConfig);
}
Map<String, String> properties = configuration.getProperties();
String kudu_master = properties.get("kudu.master.address");
@@ -203,4 +184,60 @@ public class KuduAdapter implements OuterAdapter {
}
return null;
}
private void addSyncConfigToCache(String configName, KuduMappingConfig mappingConfig) {
String k;
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
k = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "-"
+ StringUtils.trimToEmpty(mappingConfig.getGroupId()) + "_"
+ mappingConfig.getKuduMapping().getDatabase() + "-" + mappingConfig.getKuduMapping().getTable();
} else {
k = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_"
+ mappingConfig.getKuduMapping().getDatabase() + "-" + mappingConfig.getKuduMapping().getTable();
}
Map<String, KuduMappingConfig> configMap = mappingConfigCache.computeIfAbsent(k,
k1 -> new ConcurrentHashMap<>());
configMap.put(configName, mappingConfig);
}
public boolean addConfig(String fileName, KuduMappingConfig config) {
if (match(config)) {
kuduMapping.put(fileName, config);
addSyncConfigToCache(fileName, config);
FileName2KeyMapping.register(getClass().getAnnotation(SPI.class).value(), fileName,
configuration.getKey());
return true;
}
return false;
}
public void updateConfig(String fileName, KuduMappingConfig config) {
if (config.getOuterAdapterKey() != null && !config.getOuterAdapterKey()
.equals(configuration.getKey())) {
// 理论上不允许改这个 因为本身就是通过这个关联起Adapter和Config的
throw new RuntimeException("not allow to change outAdapterKey");
}
kuduMapping.put(fileName, config);
addSyncConfigToCache(fileName, config);
}
public void deleteConfig(String fileName) {
kuduMapping.remove(fileName);
for (Map<String, KuduMappingConfig> configMap : mappingConfigCache.values()) {
if (configMap != null) {
configMap.remove(fileName);
}
}
FileName2KeyMapping.unregister(getClass().getAnnotation(SPI.class).value(), fileName);
}
private boolean match(KuduMappingConfig config) {
boolean sameMatch = config.getOuterAdapterKey() != null && config.getOuterAdapterKey()
.equalsIgnoreCase(configuration.getKey());
boolean prefixMatch = config.getOuterAdapterKey() == null && configuration.getKey()
.startsWith(StringUtils
.join(new String[]{Util.AUTO_GENERATED_PREFIX, config.getDestination(),
config.getGroupId()}, '-'));
return sameMatch || prefixMatch;
}
}

View File

@@ -1,5 +1,6 @@
package com.alibaba.otter.canal.client.adapter.kudu.config;
import com.alibaba.otter.canal.client.adapter.support.YamlUtils;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
@@ -7,7 +8,6 @@ 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;
/**
@@ -30,11 +30,7 @@ public class KuduMappingConfigLoader {
Map<String, String> configContentMap = MappingConfigsLoader.loadConfigs("kudu");
configContentMap.forEach((fileName, content) -> {
KuduMappingConfig config = YmlConfigBinder.bindYmlToObj(null,
content,
KuduMappingConfig.class,
null,
envProperties);
KuduMappingConfig config = YamlUtils.ymlToObj(null, content, KuduMappingConfig.class, null, envProperties);
if (config == null) {
return;
}

View File

@@ -1,23 +1,18 @@
package com.alibaba.otter.canal.client.adapter.kudu.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.kudu.KuduAdapter;
import com.alibaba.otter.canal.client.adapter.kudu.config.KuduMappingConfig;
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
import com.alibaba.otter.canal.client.adapter.support.Util;
import com.alibaba.otter.canal.client.adapter.support.YamlUtils;
import java.io.File;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author liuyadong
@@ -74,18 +69,16 @@ public class KuduConfigMonitor {
try {
// 加载新增的配置文件
String configContent = MappingConfigsLoader.loadConfig(adapterName + File.separator + file.getName());
KuduMappingConfig config = YmlConfigBinder.bindYmlToObj(null,
configContent,
KuduMappingConfig.class,
null,
envProperties);
KuduMappingConfig config = YamlUtils
.ymlToObj(null, configContent, KuduMappingConfig.class, null, envProperties);
if (config == null) {
return;
}
config.validate();
addConfigToCache(file, config);
logger.info("Add a new kudu mapping config: {} to canal adapter", file.getName());
boolean result = kuduAdapter.addConfig(file.getName(), config);
if (result) {
logger.info("Add a new kudu mapping config: {} to canal adapter", file.getName());
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
@@ -98,25 +91,19 @@ public class KuduConfigMonitor {
try {
if (kuduAdapter.getKuduMapping().containsKey(file.getName())) {
// 加载配置文件
String configContent = MappingConfigsLoader.loadConfig(adapterName + File.separator
+ file.getName());
String configContent = MappingConfigsLoader
.loadConfig(adapterName + File.separator + file.getName());
if (configContent == null) {
onFileDelete(file);
return;
}
KuduMappingConfig config = YmlConfigBinder.bindYmlToObj(null,
configContent,
KuduMappingConfig.class,
null,
envProperties);
KuduMappingConfig config = YamlUtils
.ymlToObj(null, configContent, KuduMappingConfig.class, null, envProperties);
if (config == null) {
return;
}
config.validate();
if (kuduAdapter.getKuduMapping().containsKey(file.getName())) {
deleteConfigFromCache(file);
}
addConfigToCache(file, config);
kuduAdapter.updateConfig(file.getName(), config);
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
@@ -129,42 +116,12 @@ public class KuduConfigMonitor {
try {
if (kuduAdapter.getKuduMapping().containsKey(file.getName())) {
deleteConfigFromCache(file);
kuduAdapter.deleteConfig(file.getName());
logger.info("Delete a hbase mapping config: {} of canal adapter", file.getName());
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
/**
* 添加配置文件信息到缓存
*
* @param file
* @param config
*/
private void addConfigToCache(File file, KuduMappingConfig config) {
kuduAdapter.getKuduMapping().put(file.getName(), config);
Map<String, KuduMappingConfig> configMap = kuduAdapter.getMappingConfigCache()
.computeIfAbsent(StringUtils.trimToEmpty(config.getDestination()) + "."
+ config.getKuduMapping().getDatabase() + "." + config.getKuduMapping().getTable(),
k1 -> new HashMap<>());
configMap.put(file.getName(), config);
}
/**
* 从缓存中删除配置
*
* @param file 文件
*/
private void deleteConfigFromCache(File file) {
kuduAdapter.getKuduMapping().remove(file.getName());
for (Map<String, KuduMappingConfig> configMap : kuduAdapter.getMappingConfigCache().values()) {
if (configMap != null) {
configMap.remove(file.getName());
}
}
}
}
}

View File

@@ -10,8 +10,8 @@ import org.apache.kudu.client.KuduException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter.Feature;
import com.alibaba.otter.canal.client.adapter.kudu.config.KuduMappingConfig;
import com.alibaba.otter.canal.client.adapter.kudu.support.KuduTemplate;
import com.alibaba.otter.canal.client.adapter.support.Dml;
@@ -55,7 +55,7 @@ public class KuduSyncService {
delete(config, dml);
}
if (logger.isDebugEnabled()) {
logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
logger.debug("DML: {}", JSON.toJSONString(dml, Feature.WriteNulls));
}
}
}
@@ -118,7 +118,7 @@ public class KuduSyncService {
}
} catch (KuduException e) {
logger.error(e.getMessage());
logger.error("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
logger.error("DML: {}", JSON.toJSONString(dml, Feature.WriteNulls));
}
}
}
@@ -159,7 +159,7 @@ public class KuduSyncService {
}
} catch (KuduException e) {
logger.error(e.getMessage());
logger.error("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
logger.error("DML: {}", JSON.toJSONString(dml, Feature.WriteNulls));
}
}
@@ -201,7 +201,7 @@ public class KuduSyncService {
}
} catch (KuduException e) {
logger.error(e.getMessage());
logger.error("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
logger.error("DML: {}", JSON.toJSONString(dml, Feature.WriteNulls));
}
}
}

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.6-SNAPSHOT</version>
<version>1.1.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -61,7 +61,7 @@
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc6</artifactId>
</dependency>
<dependency>
@@ -207,6 +207,19 @@
<classifier>jar-with-dependencies</classifier>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba.otter</groupId>
<artifactId>connector.pulsarmq</artifactId>
<version>${project.version}</version>
<exclusions>
<exclusion>
<artifactId>*</artifactId>
<groupId>*</groupId>
</exclusion>
</exclusions>
<classifier>jar-with-dependencies</classifier>
<scope>provided</scope>
</dependency>
</dependencies>
<build>

View File

@@ -56,11 +56,27 @@ in
exit;;
esac
JavaVersion=`$JAVA -version 2>&1 |awk 'NR==1{ gsub(/"/,""); print $3 }' | awk -F '.' '{print $1}'`
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"
JAVA_OPTS="$JAVA_OPTS -Xss256k -XX:+AggressiveOpts -XX:-UseBiasedLocking -XX:-OmitStackTraceInFastThrow -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$base/logs"
if [ $JavaVersion -ge 11 ] ; then
#JAVA_OPTS="$JAVA_OPTS -Xlog:gc*:$base_log/gc.log:time "
JAVA_OPTS="$JAVA_OPTS"
else
JAVA_OPTS="-server -Xms1024m -Xmx1024m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:MaxPermSize=128m "
#JAVA_OPTS="$JAVA_OPTS -Xloggc:$base/logs/canal/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime"
JAVA_OPTS="$JAVA_OPTS -XX:+UseFastAccessorMethods -XX:+PrintAdaptiveSizePolicy -XX:+PrintTenuringDistribution"
fi
if [ -n "$str" ]; then
if [ $JavaVersion -ge 11 ] ; then
# For G1
JAVA_OPTS="-server -Xms2g -Xmx3g -XX:+UseG1GC -XX:MaxGCPauseMillis=250 -XX:+UseGCOverheadLimit -XX:+ExplicitGCInvokesConcurrent $JAVA_OPTS"
else
JAVA_OPTS="-server -Xms2g -Xmx3g -Xmn1g -XX:SurvivorRatio=2 -XX:PermSize=96m -XX:MaxPermSize=256m -XX:MaxTenuringThreshold=15 -XX:+DisableExplicitGC $JAVA_OPTS"
fi
else
JAVA_OPTS="-server -Xms1024m -Xmx1024m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:MaxPermSize=128m $JAVA_OPTS"
fi
JAVA_OPTS=" $JAVA_OPTS -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8"

View File

@@ -13,6 +13,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CanalAdapterApplication {
public static void main(String[] args) {
// 支持rocketmq client 配置日志路径
System.setProperty("rocketmq.client.logUseSlf4j","true");
SpringApplication application = new SpringApplication(CanalAdapterApplication.class);
application.setBannerMode(Banner.Mode.OFF);

View File

@@ -0,0 +1,23 @@
package com.alibaba.otter.canal.adapter.launcher.config;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.alibaba.druid.support.http.StatViewServlet;
@Configuration
public class DruidConfig {
@Bean
public ServletRegistrationBean<StatViewServlet> statViewServlet(){
ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>( new StatViewServlet(),"/druid/*");
Map<String,String> initParams = new HashMap<>();
initParams.put("allow","");
bean.setInitParameters(initParams);
return bean;
}
}

View File

@@ -22,6 +22,7 @@ import com.alibaba.otter.canal.client.adapter.OuterAdapter;
import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig;
import com.alibaba.otter.canal.client.adapter.support.ExtensionLoader;
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
import com.alibaba.otter.canal.client.adapter.support.Util;
/**
* 外部适配器的加载器
@@ -50,158 +51,43 @@ public class CanalAdapterLoader {
for (CanalClientConfig.CanalAdapter canalAdapter : canalClientConfig.getCanalAdapters()) {
for (CanalClientConfig.Group group : canalAdapter.getGroups()) {
int autoGenId = 0;
List<List<OuterAdapter>> canalOuterAdapterGroups = new CopyOnWriteArrayList<>();
List<OuterAdapter> canalOuterAdapters = new CopyOnWriteArrayList<>();
for (OuterAdapterConfig config : group.getOuterAdapters()) {
// 保证一定有key
if (StringUtils.isEmpty(config.getKey())) {
String key = StringUtils.join(
new String[] { Util.AUTO_GENERATED_PREFIX, canalAdapter.getInstance(), group.getGroupId(),
String.valueOf(autoGenId) },
'-');
//gen keyId
config.setKey(key);
}
autoGenId++;
loadAdapter(config, canalOuterAdapters);
}
canalOuterAdapterGroups.add(canalOuterAdapters);
AdapterProcessor adapterProcessor = canalAdapterProcessors.computeIfAbsent(canalAdapter.getInstance()
+ "|"
+ StringUtils.trimToEmpty(group.getGroupId()),
AdapterProcessor adapterProcessor = canalAdapterProcessors.computeIfAbsent(
canalAdapter.getInstance() + "|" + StringUtils.trimToEmpty(group.getGroupId()),
f -> new AdapterProcessor(canalClientConfig,
canalAdapter.getInstance(),
group.getGroupId(),
canalOuterAdapterGroups));
adapterProcessor.start();
logger.info("Start adapter for canal-client mq topic: {} succeed", canalAdapter.getInstance() + "-"
+ group.getGroupId());
logger.info("Start adapter for canal-client mq topic: {} succeed",
canalAdapter.getInstance() + "-" + group.getGroupId());
}
}
// if ("tcp".equalsIgnoreCase(canalClientConfig.getMode())) {
// // 初始化canal-client的适配器
// for (CanalClientConfig.CanalAdapter canalAdapter :
// canalClientConfig.getCanalAdapters()) {
// List<List<OuterAdapter>> canalOuterAdapterGroups = new
// CopyOnWriteArrayList<>();
//
// for (CanalClientConfig.Group connectorGroup :
// canalAdapter.getGroups()) {
// List<OuterAdapter> canalOutConnectors = new CopyOnWriteArrayList<>();
// for (OuterAdapterConfig c : connectorGroup.getOuterAdapters()) {
// loadAdapter(c, canalOutConnectors);
// }
// canalOuterAdapterGroups.add(canalOutConnectors);
// }
// CanalAdapterWorker worker;
// if (StringUtils.isNotEmpty(canalServerHost)) {
// worker = new CanalAdapterWorker(canalClientConfig,
// canalAdapter.getInstance(),
// canalServerHost,
// zkHosts,
// canalOuterAdapterGroups);
// } else if (zkHosts != null) {
// worker = new CanalAdapterWorker(canalClientConfig,
// canalAdapter.getInstance(),
// zkHosts,
// canalOuterAdapterGroups);
// } else {
// throw new RuntimeException("No canal server connector found");
// }
// canalWorkers.put(canalAdapter.getInstance(), worker);
// worker.start();
// logger.info("Start adapter for canal instance: {} succeed",
// canalAdapter.getInstance());
// }
// } else if ("kafka".equalsIgnoreCase(canalClientConfig.getMode())) {
// // 初始化canal-client-kafka的适配器
// for (CanalClientConfig.CanalAdapter canalAdapter :
// canalClientConfig.getCanalAdapters()) {
// for (CanalClientConfig.Group group : canalAdapter.getGroups()) {
// List<List<OuterAdapter>> canalOuterAdapterGroups = new
// CopyOnWriteArrayList<>();
// List<OuterAdapter> canalOuterAdapters = new CopyOnWriteArrayList<>();
// for (OuterAdapterConfig config : group.getOuterAdapters()) {
// loadAdapter(config, canalOuterAdapters);
// }
// canalOuterAdapterGroups.add(canalOuterAdapters);
//
// CanalAdapterKafkaWorker canalKafkaWorker = new
// CanalAdapterKafkaWorker(canalClientConfig,
// canalClientConfig.getMqServers(),
// canalAdapter.getInstance(),
// group.getGroupId(),
// canalOuterAdapterGroups,
// canalClientConfig.getFlatMessage());
// canalMQWorker.put(canalAdapter.getInstance() + "-kafka-" +
// group.getGroupId(), canalKafkaWorker);
// canalKafkaWorker.start();
// logger.info("Start adapter for canal-client mq topic: {} succeed",
// canalAdapter.getInstance() + "-" + group.getGroupId());
// }
// }
// } else if ("rocketMQ".equalsIgnoreCase(canalClientConfig.getMode()))
// {
// // 初始化canal-client-rocketMQ的适配器
// for (CanalClientConfig.CanalAdapter canalAdapter :
// canalClientConfig.getCanalAdapters()) {
// for (CanalClientConfig.Group group : canalAdapter.getGroups()) {
// List<List<OuterAdapter>> canalOuterAdapterGroups = new
// CopyOnWriteArrayList<>();
// List<OuterAdapter> canalOuterAdapters = new CopyOnWriteArrayList<>();
// for (OuterAdapterConfig config : group.getOuterAdapters()) {
// loadAdapter(config, canalOuterAdapters);
// }
// canalOuterAdapterGroups.add(canalOuterAdapters);
// CanalAdapterRocketMQWorker rocketMQWorker = new
// CanalAdapterRocketMQWorker(canalClientConfig,
// canalClientConfig.getMqServers(),
// canalAdapter.getInstance(),
// group.getGroupId(),
// canalOuterAdapterGroups,
// canalClientConfig.getAccessKey(),
// canalClientConfig.getSecretKey(),
// canalClientConfig.getFlatMessage(),
// canalClientConfig.isEnableMessageTrace(),
// canalClientConfig.getCustomizedTraceTopic(),
// canalClientConfig.getAccessChannel(),
// canalClientConfig.getNamespace());
// canalMQWorker.put(canalAdapter.getInstance() + "-rocketmq-" +
// group.getGroupId(), rocketMQWorker);
// rocketMQWorker.start();
//
// logger.info("Start adapter for canal-client mq topic: {} succeed",
// canalAdapter.getInstance() + "-" + group.getGroupId());
// }
// }
// } else if ("rabbitMQ".equalsIgnoreCase(canalClientConfig.getMode()))
// {
// // 初始化canal-client-rabbitMQ的适配器
// for (CanalClientConfig.CanalAdapter canalAdapter :
// canalClientConfig.getCanalAdapters()) {
// for (CanalClientConfig.Group group : canalAdapter.getGroups()) {
// List<List<OuterAdapter>> canalOuterAdapterGroups = new
// CopyOnWriteArrayList<>();
// List<OuterAdapter> canalOuterAdapters = new CopyOnWriteArrayList<>();
// for (OuterAdapterConfig config : group.getOuterAdapters()) {
// loadAdapter(config, canalOuterAdapters);
// }
// canalOuterAdapterGroups.add(canalOuterAdapters);
// CanalAdapterRabbitMQWorker rabbitMQWork = new
// CanalAdapterRabbitMQWorker(canalClientConfig,
// canalOuterAdapterGroups,
// canalAdapter.getInstance(),
// group.getGroupId(),
// canalClientConfig.getFlatMessage());
// canalMQWorker.put(canalAdapter.getInstance() + "-rabbitmq-" +
// group.getGroupId(), rabbitMQWork);
// rabbitMQWork.start();
//
// logger.info("Start adapter for canal-client mq topic: {} succeed",
// canalAdapter.getInstance() + "-" + group.getGroupId());
// }
// }
// // CanalAdapterRabbitMQWork
// }
}
private void loadAdapter(OuterAdapterConfig config, List<OuterAdapter> canalOutConnectors) {
try {
OuterAdapter adapter;
adapter = loader.getExtension(config.getName(), StringUtils.trimToEmpty(config.getKey()));
adapter = loader.getExtension(config.getName(), config.getKey());
ClassLoader cl = Thread.currentThread().getContextClassLoader();
// 替换ClassLoader

View File

@@ -1,15 +1,21 @@
package com.alibaba.otter.canal.adapter.launcher.rest;
import com.alibaba.otter.canal.adapter.launcher.common.EtlLock;
import com.alibaba.otter.canal.adapter.launcher.common.SyncSwitch;
import com.alibaba.otter.canal.adapter.launcher.config.AdapterCanalConfig;
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
import com.alibaba.otter.canal.client.adapter.support.ExtensionLoader;
import com.alibaba.otter.canal.client.adapter.support.FileName2KeyMapping;
import com.alibaba.otter.canal.client.adapter.support.Result;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
@@ -19,14 +25,6 @@ import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.otter.canal.adapter.launcher.common.EtlLock;
import com.alibaba.otter.canal.adapter.launcher.common.SyncSwitch;
import com.alibaba.otter.canal.adapter.launcher.config.AdapterCanalConfig;
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
import com.alibaba.otter.canal.client.adapter.support.EtlResult;
import com.alibaba.otter.canal.client.adapter.support.ExtensionLoader;
import com.alibaba.otter.canal.client.adapter.support.Result;
/**
* 适配器操作Rest
*
@@ -66,6 +64,9 @@ public class CommonRest {
@PostMapping("/etl/{type}/{key}/{task}")
public EtlResult etl(@PathVariable String type, @PathVariable String key, @PathVariable String task,
@RequestParam(name = "params", required = false) String params) {
if (key == null) {
key = FileName2KeyMapping.getKey(type, task);
}
OuterAdapter adapter = loader.getExtension(type, key);
String destination = adapter.getDestination(task);
String lockKey = destination == null ? task : destination;
@@ -133,6 +134,9 @@ public class CommonRest {
*/
@GetMapping("/count/{type}/{key}/{task}")
public Map<String, Object> count(@PathVariable String type, @PathVariable String key, @PathVariable String task) {
if (key == null) {
key = FileName2KeyMapping.getKey(type, task);
}
OuterAdapter adapter = loader.getExtension(type, key);
return adapter.count(task);
}

View File

@@ -11,7 +11,7 @@ canal.conf:
flatMessage: true
zookeeperHosts:
syncBatchSize: 1000
retries: 0
retries: -1
timeout:
accessKey:
secretKey:
@@ -64,6 +64,8 @@ canal.conf:
# jdbc.url: jdbc:mysql://127.0.0.1:3306/mytest2?useUnicode=true
# jdbc.username: root
# jdbc.password: 121212
# druid.stat.enable: false
# druid.stat.slowSqlMillis: 1000
# - name: rdb
# key: oracle1
# properties:
@@ -91,14 +93,14 @@ canal.conf:
# mode: transport # or rest
# # security.auth: test:123456 # only used for rest mode
# cluster.name: elasticsearch
# - name: kudu
# key: kudu
# properties:
# kudu.master.address: 127.0.0.1 # ',' split multi address
# - name: phoenix
# key: phoenix
# properties:
# jdbc.driverClassName: org.apache.phoenix.jdbc.PhoenixDriver
# jdbc.url: jdbc:phoenix:127.0.0.1:2181:/hbase/db
# jdbc.username:
# jdbc.password:
# - name: kudu
# key: kudu
# properties:
# kudu.master.address: 127.0.0.1 # ',' split multi address
# - name: phoenix
# key: phoenix
# properties:
# jdbc.driverClassName: org.apache.phoenix.jdbc.PhoenixDriver
# jdbc.url: jdbc:phoenix:127.0.0.1:2181:/hbase/db
# jdbc.username:
# jdbc.password:

View File

@@ -1,6 +1,6 @@
#canal:
# manager:
# jdbc:
# url: jdbc:mysql://127.0.0.1:3306/canal_manager?useUnicode=true&characterEncoding=UTF-8
# username: root
# password: 121212
canal:
manager:
jdbc:
url: jdbc:mysql://127.0.0.1:3306/canal_manager?useUnicode=true&characterEncoding=UTF-8
username: canal
password: canal

View File

@@ -34,6 +34,23 @@
</sift>
</appender>
<appender name="RocketmqClientAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>../logs/adapter/rocketmq_client.log</file>
<rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- rollover daily -->
<fileNamePattern>../logs/adapter/%d{yyyy-MM-dd}/rocketmq_client-%d{yyyy-MM-dd}-%i.log.gz</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- or whenever the file size reaches 100MB -->
<maxFileSize>512MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder charset="UTF-8">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n</pattern>
</encoder>
</appender>
<logger name="com.alibaba.otter.canal.client.adapter.hbase" additivity="false">
<level value="DEBUG" />
<appender-ref ref="STDOUT"/>
@@ -54,6 +71,10 @@
<appender-ref ref="STDOUT"/>
<appender-ref ref="CANAL-ROOT" />
</logger>
<logger name="RocketmqClient" additivity="false">
<level value="INFO" />
<appender-ref ref="RocketmqClientAppender" />
</logger>
<root level="INFO">
<appender-ref ref="STDOUT"/>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.6-SNAPSHOT</version>
<version>1.1.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -6,8 +6,8 @@ import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter.Feature;
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
import com.alibaba.otter.canal.client.adapter.support.Dml;
import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig;
@@ -36,7 +36,7 @@ public class LoggerAdapterExample implements OuterAdapter {
}
public void sync(Dml dml) {
logger.info("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
logger.info("DML: {}", JSON.toJSONString(dml, Feature.WriteNulls));
}
@Override

View File

@@ -1,11 +1,9 @@
<?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">
<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.6-SNAPSHOT</version>
<version>1.1.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -41,50 +39,42 @@
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.12</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.9</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
<scope>provided</scope>
</dependency>
</dependencies>
@@ -123,19 +113,19 @@
<!-- <tasks>-->
<!-- <copy todir="${project.basedir}/../launcher/target/classes/phoenix" overwrite="true">-->
<!-- <fileset dir="${project.basedir}/target/classes/phoenix" erroronmissingdir="true">-->
<!-- <include name="*.yml"/>-->
<!-- <include name="*.yml" />-->
<!-- </fileset>-->
<!-- </copy>-->
<!-- </tasks>-->
<tasks>
<copy todir="${project.basedir}/../launcher/target/canal-adapter/conf/phoenix" overwrite="true">
<fileset dir="${project.basedir}/target/classes/phoenix" erroronmissingdir="true">
<include name="*.yml"/>
<include name="*.yml" />
</fileset>
</copy>
<copy todir="${project.basedir}/../launcher/target/canal-adapter/plugin" overwrite="true">
<fileset dir="${project.basedir}/target/" erroronmissingdir="true">
<include name="*with-dependencies.jar"/>
<include name="*with-dependencies.jar" />
</fileset>
</copy>
<copy todir="${project.basedir}/../launcher/target/classes/phoenix" overwrite="true">

View File

@@ -43,7 +43,7 @@ public class PhoenixAdapter implements OuterAdapter {
private Properties envProperties;
private OuterAdapterConfig configuration;
public Map<String, MappingConfig> getPhoenixMapping() {
return phoenixMapping;
@@ -64,14 +64,11 @@ public class PhoenixAdapter implements OuterAdapter {
@Override
public void init(OuterAdapterConfig configuration, Properties envProperties) {
this.envProperties = envProperties;
this.configuration = configuration;
Map<String, MappingConfig> phoenixMappingTmp = ConfigLoader.load(envProperties);
// 过滤不匹配的key的配置
phoenixMappingTmp.forEach((key, mappingConfig) -> {
if ((mappingConfig.getOuterAdapterKey() == null && configuration.getKey() == null)
|| (mappingConfig.getOuterAdapterKey() != null
&& mappingConfig.getOuterAdapterKey().equalsIgnoreCase(configuration.getKey()))) {
phoenixMapping.put(key, mappingConfig);
}
phoenixMappingTmp.forEach((key, config) -> {
addConfig(key, config);
});
if (phoenixMapping.isEmpty()) {
@@ -80,24 +77,6 @@ public class PhoenixAdapter implements OuterAdapter {
logger.info("[{}]phoenix config mapping: {}", this, phoenixMapping.keySet());
}
for (Map.Entry<String, MappingConfig> entry : phoenixMapping.entrySet()) {
String configName = entry.getKey();
MappingConfig mappingConfig = entry.getValue();
String key;
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "-"
+ StringUtils.trimToEmpty(mappingConfig.getGroupId()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-" + mappingConfig.getDbMapping().getTable().toLowerCase();
} else {
key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-" + mappingConfig.getDbMapping().getTable().toLowerCase();
}
Map<String, MappingConfig> configMap = mappingConfigCache.computeIfAbsent(key,
k1 -> new ConcurrentHashMap<>());
configMap.put(configName, mappingConfig);
}
Map<String, String> properties = configuration.getProperties();
DriverClass= properties.get("jdbc.driverClassName");
@@ -288,4 +267,60 @@ public class PhoenixAdapter implements OuterAdapter {
phoenixSyncService.close();
}
}
private void addSyncConfigToCache(String configName, MappingConfig mappingConfig) {
String key;
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "-"
+ StringUtils.trimToEmpty(mappingConfig.getGroupId()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-" + mappingConfig.getDbMapping().getTable().toLowerCase();
} else {
key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-" + mappingConfig.getDbMapping().getTable().toLowerCase();
}
Map<String, MappingConfig> configMap = mappingConfigCache.computeIfAbsent(key,
k1 -> new ConcurrentHashMap<>());
configMap.put(configName, mappingConfig);
}
public boolean addConfig(String fileName, MappingConfig config) {
if (match(config)) {
phoenixMapping.put(fileName, config);
addSyncConfigToCache(fileName, config);
FileName2KeyMapping.register(getClass().getAnnotation(SPI.class).value(), fileName,
configuration.getKey());
return true;
}
return false;
}
public void updateConfig(String fileName, MappingConfig config) {
if (config.getOuterAdapterKey() != null && !config.getOuterAdapterKey()
.equals(configuration.getKey())) {
// 理论上不允许改这个 因为本身就是通过这个关联起Adapter和Config的
throw new RuntimeException("not allow to change outAdapterKey");
}
phoenixMapping.put(fileName, config);
addSyncConfigToCache(fileName, config);
}
public void deleteConfig(String fileName) {
phoenixMapping.remove(fileName);
for (Map<String, MappingConfig> configMap : mappingConfigCache.values()) {
if (configMap != null) {
configMap.remove(fileName);
}
}
FileName2KeyMapping.unregister(getClass().getAnnotation(SPI.class).value(), fileName);
}
private boolean match(MappingConfig config) {
boolean sameMatch = config.getOuterAdapterKey() != null && config.getOuterAdapterKey()
.equalsIgnoreCase(configuration.getKey());
boolean prefixMatch = config.getOuterAdapterKey() == null && configuration.getKey()
.startsWith(StringUtils
.join(new String[]{Util.AUTO_GENERATED_PREFIX, config.getDestination(),
config.getGroupId()}, '-'));
return sameMatch || prefixMatch;
}
}

View File

@@ -1,7 +1,7 @@
package com.alibaba.otter.canal.client.adapter.phoenix.config;
import com.alibaba.otter.canal.client.adapter.config.YmlConfigBinder;
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
import com.alibaba.otter.canal.client.adapter.support.YamlUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -28,8 +28,7 @@ public class ConfigLoader {
Map<String, String> configContentMap = MappingConfigsLoader.loadConfigs("phoenix");
configContentMap.forEach((fileName, content) -> {
MappingConfig config = YmlConfigBinder
.bindYmlToObj(null, content, MappingConfig.class, null, envProperties);
MappingConfig config = YamlUtils.ymlToObj(null, content, MappingConfig.class, null, envProperties);
if (config == null) {
return;
}

View File

@@ -2,36 +2,32 @@ package com.alibaba.otter.canal.client.adapter.phoenix.monitor;
import com.alibaba.otter.canal.client.adapter.phoenix.PhoenixAdapter;
import com.alibaba.otter.canal.client.adapter.phoenix.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.config.YmlConfigBinder;
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
import com.alibaba.otter.canal.client.adapter.support.Util;
import com.alibaba.otter.canal.client.adapter.support.YamlUtils;
import java.io.File;
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 java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* phoenix config monitor
*/
public class PhoenixConfigMonitor {
private static final Logger logger = LoggerFactory.getLogger(PhoenixConfigMonitor.class);
private static final Logger logger = LoggerFactory.getLogger(PhoenixConfigMonitor.class);
private static final String adapterName = "phoenix"; //相应组件名字
private static final String adapterName = "phoenix"; // 相应组件名字
private String key;
private String key;
private PhoenixAdapter phoenixAdapter; //相应适配器名实现类
private PhoenixAdapter phoenixAdapter; // 相应适配器名实现类
private Properties envProperties;
private Properties envProperties;
private FileAlterationMonitor fileMonitor;
@@ -42,7 +38,7 @@ public class PhoenixConfigMonitor {
File confDir = Util.getConfDirPath(adapterName);
try {
FileAlterationObserver observer = new FileAlterationObserver(confDir,
FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml")));
FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml")));
FileListener listener = new FileListener();
observer.addListener(listener);
fileMonitor = new FileAlterationMonitor(3000, observer);
@@ -69,16 +65,14 @@ public class PhoenixConfigMonitor {
try {
// 加载新增的配置文件
String configContent = MappingConfigsLoader.loadConfig(adapterName + File.separator + file.getName());
MappingConfig config = YmlConfigBinder
.bindYmlToObj(null, configContent, MappingConfig.class, null, envProperties);
MappingConfig config = YamlUtils
.ymlToObj(null, configContent, MappingConfig.class, null, envProperties);
if (config == null) {
return;
}
config.validate();
if ((key == null && config.getOuterAdapterKey() == null)
|| (key != null && key.equals(config.getOuterAdapterKey()))) {
addConfigToCache(file, config);
boolean result = phoenixAdapter.addConfig(file.getName(), config);
if (result) {
logger.info("Add a new phoenix mapping config: {} to canal adapter", file.getName());
}
} catch (Exception e) {
@@ -94,27 +88,18 @@ public class PhoenixConfigMonitor {
if (phoenixAdapter.getPhoenixMapping().containsKey(file.getName())) {
// 加载配置文件
String configContent = MappingConfigsLoader
.loadConfig(adapterName + File.separator + file.getName());
.loadConfig(adapterName + File.separator + file.getName());
if (configContent == null) {
onFileDelete(file);
return;
}
MappingConfig config = YmlConfigBinder
.bindYmlToObj(null, configContent, MappingConfig.class, null, envProperties);
MappingConfig config = YamlUtils
.ymlToObj(null, configContent, MappingConfig.class, null, envProperties);
if (config == null) {
return;
}
config.validate();
if ((key == null && config.getOuterAdapterKey() == null)
|| (key != null && key.equals(config.getOuterAdapterKey()))) {
if (phoenixAdapter.getPhoenixMapping().containsKey(file.getName())) {
deleteConfigFromCache(file);
}
addConfigToCache(file, config);
} else {
// 不能修改outerAdapterKey
throw new RuntimeException("Outer adapter key not allowed modify");
}
phoenixAdapter.updateConfig(file.getName(), config);
logger.info("Change a phoenix mapping config: {} of canal adapter", file.getName());
}
} catch (Exception e) {
@@ -128,40 +113,12 @@ public class PhoenixConfigMonitor {
try {
if (phoenixAdapter.getPhoenixMapping().containsKey(file.getName())) {
deleteConfigFromCache(file);
phoenixAdapter.deleteConfig(file.getName());
logger.info("Delete a phoenix mapping config: {} of canal adapter", file.getName());
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
private void addConfigToCache(File file, MappingConfig mappingConfig) {
if (mappingConfig == null || mappingConfig.getDbMapping() == null) {
return;
}
phoenixAdapter.getPhoenixMapping().put(file.getName(), mappingConfig);
Map<String, MappingConfig> configMap = phoenixAdapter.getMappingConfigCache()
.computeIfAbsent(StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-"
+ mappingConfig.getDbMapping().getTable().toLowerCase(),
k1 -> new HashMap<>());
configMap.put(file.getName(), mappingConfig);
}
private void deleteConfigFromCache(File file) {
logger.info("deleteConfigFromCache: {}", file.getName());
MappingConfig mappingConfig = phoenixAdapter.getPhoenixMapping().remove(file.getName());
if (mappingConfig == null || mappingConfig.getDbMapping() == null) {
return;
}
for (Map<String, MappingConfig> configMap : phoenixAdapter.getMappingConfigCache().values()) {
if (configMap != null) {
configMap.remove(file.getName());
}
}
}
}
}

View File

@@ -6,8 +6,8 @@ import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.statement.*;
import com.alibaba.druid.sql.parser.ParserException;
import com.alibaba.druid.util.JdbcConstants;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter.Feature;
import com.alibaba.otter.canal.client.adapter.phoenix.config.ConfigurationManager;
import com.alibaba.otter.canal.client.adapter.phoenix.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.phoenix.config.MappingConfig.DbMapping;
@@ -208,7 +208,7 @@ public class PhoenixSyncService {
for (Map.Entry<String, MappingConfig> entry : configMap.entrySet()) {
MappingConfig config = entry.getValue();
if (config.isDebug()) {
logger.info("DML: {} {}", entry.getKey(), JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
logger.info("DML: {} {}", entry.getKey(), JSON.toJSONString(dml, Feature.WriteNulls));
}
if (config.getConcurrent()) {
//并行同步
@@ -257,7 +257,7 @@ public class PhoenixSyncService {
} else if (type != null && type.equalsIgnoreCase("TRUNCATE")) {
truncate(batchExecutor, config);
} else if (logger.isInfoEnabled()){
logger.info("SingleDml: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
logger.info("SingleDml: {}", JSON.toJSONString(dml, Feature.WriteNulls));
}
} catch (SQLException e) {
logger.error("sync error: " + e.getMessage(), e);
@@ -268,7 +268,7 @@ public class PhoenixSyncService {
private void alter(BatchExecutor batchExecutor, MappingConfig config, Dml dml, List<SQLStatement> stmtList, String configFile) throws SQLException {
if (config.isDebug()) {
logger.info("DML: {} {}", configFile, JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
logger.info("DML: {} {}", configFile, JSON.toJSONString(dml, Feature.WriteNulls));
}
DbMapping dbMapping = config.getDbMapping();
if (!dbMapping.isAlter()) {

View File

@@ -4,12 +4,12 @@
<parent>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId>
<version>1.1.6-SNAPSHOT</version>
<version>1.1.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal.client-adapter</artifactId>
<version>1.1.6-SNAPSHOT</version>
<version>1.1.7-SNAPSHOT</version>
<packaging>pom</packaging>
<name>canal client adapter module for otter ${project.version}</name>
@@ -20,6 +20,7 @@
<java_source_version>1.8</java_source_version>
<java_target_version>1.8</java_target_version>
<file_encoding>UTF-8</file_encoding>
<log4j_version>2.17.0</log4j_version>
</properties>
<modules>
@@ -112,35 +113,9 @@
<version>2.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.5.RELEASE</version>
<scope>test</scope>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
@@ -182,9 +157,9 @@
<version>42.1.4</version>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.3</version>
<version>11.2.0.4</version>
</dependency>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
@@ -212,6 +187,12 @@
<artifactId>curator-recipes</artifactId>
<version>2.10.0</version>
</dependency>
<!-- 单独指定guava版本,兼容curator-client -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
@@ -270,7 +251,21 @@
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j_version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4j_version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>${log4j_version}</version>
</dependency>
</dependencies>
</dependencyManagement>

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.6-SNAPSHOT</version>
<version>1.1.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -30,7 +30,7 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc6</artifactId>
<scope>test</scope>
</dependency>

View File

@@ -1,7 +1,9 @@
package com.alibaba.otter.canal.client.adapter.rdb;
import com.alibaba.otter.canal.client.adapter.support.FileName2KeyMapping;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -13,7 +15,9 @@ import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.util.JdbcUtils;
import com.alibaba.otter.canal.client.adapter.OuterAdapter;
import com.alibaba.otter.canal.client.adapter.rdb.config.ConfigLoader;
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
@@ -53,6 +57,8 @@ public class RdbAdapter implements OuterAdapter {
private Properties envProperties;
private OuterAdapterConfig configuration;
public Map<String, MappingConfig> getRdbMapping() {
return rdbMapping;
}
@@ -73,46 +79,22 @@ public class RdbAdapter implements OuterAdapter {
@Override
public void init(OuterAdapterConfig configuration, Properties envProperties) {
this.envProperties = envProperties;
this.configuration = configuration;
// 从jdbc url获取db类型
Map<String, String> properties = configuration.getProperties();
String dbType = JdbcUtils.getDbType(properties.get("jdbc.url"), null);
Map<String, MappingConfig> rdbMappingTmp = ConfigLoader.load(envProperties);
// 过滤不匹配的key的配置
rdbMappingTmp.forEach((key, mappingConfig) -> {
if ((mappingConfig.getOuterAdapterKey() == null && configuration.getKey() == null)
|| (mappingConfig.getOuterAdapterKey() != null && mappingConfig.getOuterAdapterKey()
.equalsIgnoreCase(configuration.getKey()))) {
rdbMapping.put(key, mappingConfig);
}
rdbMappingTmp.forEach((key, config) -> {
addConfig(key, config);
});
if (rdbMapping.isEmpty()) {
throw new RuntimeException("No rdb adapter found for config key: " + configuration.getKey());
}
for (Map.Entry<String, MappingConfig> entry : rdbMapping.entrySet()) {
String configName = entry.getKey();
MappingConfig mappingConfig = entry.getValue();
if (!mappingConfig.getDbMapping().getMirrorDb()) {
String key;
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "-"
+ StringUtils.trimToEmpty(mappingConfig.getGroupId()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-" + mappingConfig.getDbMapping().getTable();
} else {
key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-" + mappingConfig.getDbMapping().getTable();
}
Map<String, MappingConfig> configMap = mappingConfigCache.computeIfAbsent(key,
k1 -> new ConcurrentHashMap<>());
configMap.put(configName, mappingConfig);
} else {
// mirrorDB
String key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "."
+ mappingConfig.getDbMapping().getDatabase();
mirrorDbConfigCache.put(key, MirrorDbConfig.create(configName, mappingConfig));
}
}
// 初始化连接池
Map<String, String> properties = configuration.getProperties();
dataSource = new DruidDataSource();
dataSource.setDriverClassName(properties.get("jdbc.driverClassName"));
dataSource.setUrl(properties.get("jdbc.url"));
@@ -125,10 +107,20 @@ public class RdbAdapter implements OuterAdapter {
dataSource.setTimeBetweenEvictionRunsMillis(60000);
dataSource.setMinEvictableIdleTimeMillis(300000);
dataSource.setUseUnfairLock(true);
dataSource.setDbType(dbType);
// List<String> array = new ArrayList<>();
// array.add("set names utf8mb4;");
// dataSource.setConnectionInitSqls(array);
if ("true".equals(properties.getOrDefault("druid.stat.enable", "true"))) {
StatFilter statFilter = new StatFilter();
statFilter.setSlowSqlMillis(Long.parseLong(properties.getOrDefault("druid.stat.slowSqlMillis", "1000")));
statFilter.setMergeSql(true);
statFilter.setLogSlowSql(true);
dataSource.setProxyFilters(Collections.singletonList(statFilter));
}
try {
dataSource.init();
} catch (SQLException e) {
@@ -165,7 +157,9 @@ public class RdbAdapter implements OuterAdapter {
return;
}
try {
rdbSyncService.sync(mappingConfigCache, dmls, envProperties);
if (!mappingConfigCache.isEmpty()) {
rdbSyncService.sync(mappingConfigCache, dmls, envProperties);
}
rdbMirrorDbSyncService.sync(dmls);
} catch (Exception e) {
throw new RuntimeException(e);
@@ -226,7 +220,7 @@ public class RdbAdapter implements OuterAdapter {
public Map<String, Object> count(String task) {
MappingConfig config = rdbMapping.get(task);
MappingConfig.DbMapping dbMapping = config.getDbMapping();
String sql = "SELECT COUNT(1) AS cnt FROM " + SyncUtil.getDbTableName(dbMapping);
String sql = "SELECT COUNT(1) AS cnt FROM " + SyncUtil.getDbTableName(dbMapping, dataSource.getDbType());
Connection conn = null;
Map<String, Object> res = new LinkedHashMap<>();
try {
@@ -252,7 +246,7 @@ public class RdbAdapter implements OuterAdapter {
}
}
}
res.put("targetTable", SyncUtil.getDbTableName(dbMapping));
res.put("targetTable", SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()));
return res;
}
@@ -289,4 +283,67 @@ public class RdbAdapter implements OuterAdapter {
dataSource.close();
}
}
private void addSyncConfigToCache(String configName, MappingConfig mappingConfig) {
if (!mappingConfig.getDbMapping().getMirrorDb()) {
String key;
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "-"
+ StringUtils.trimToEmpty(mappingConfig.getGroupId()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-" + mappingConfig.getDbMapping().getTable();
} else {
key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-" + mappingConfig.getDbMapping().getTable();
}
Map<String, MappingConfig> configMap = mappingConfigCache.computeIfAbsent(key,
k1 -> new ConcurrentHashMap<>());
configMap.put(configName, mappingConfig);
} else {
// mirrorDB
String key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "."
+ mappingConfig.getDbMapping().getDatabase();
mirrorDbConfigCache.put(key, MirrorDbConfig.create(configName, mappingConfig));
}
}
public boolean addConfig(String fileName, MappingConfig config) {
if (match(config)) {
rdbMapping.put(fileName, config);
addSyncConfigToCache(fileName, config);
FileName2KeyMapping.register(getClass().getAnnotation(SPI.class).value(), fileName,
configuration.getKey());
return true;
}
return false;
}
public void updateConfig(String fileName, MappingConfig config) {
if (config.getOuterAdapterKey() != null && !config.getOuterAdapterKey()
.equals(configuration.getKey())) {
// 理论上不允许改这个 因为本身就是通过这个关联起Adapter和Config的
throw new RuntimeException("not allow to change outAdapterKey");
}
rdbMapping.put(fileName, config);
addSyncConfigToCache(fileName, config);
}
public void deleteConfig(String fileName) {
rdbMapping.remove(fileName);
for (Map<String, MappingConfig> configMap : mappingConfigCache.values()) {
if (configMap != null) {
configMap.remove(fileName);
}
}
FileName2KeyMapping.unregister(getClass().getAnnotation(SPI.class).value(), fileName);
}
private boolean match(MappingConfig config) {
boolean sameMatch = config.getOuterAdapterKey() != null && config.getOuterAdapterKey()
.equalsIgnoreCase(configuration.getKey());
boolean prefixMatch = config.getOuterAdapterKey() == null && configuration.getKey()
.startsWith(StringUtils
.join(new String[]{Util.AUTO_GENERATED_PREFIX, config.getDestination(),
config.getGroupId()}, '-'));
return sameMatch || prefixMatch;
}
}

View File

@@ -1,5 +1,6 @@
package com.alibaba.otter.canal.client.adapter.rdb.config;
import com.alibaba.otter.canal.client.adapter.support.YamlUtils;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
@@ -7,7 +8,6 @@ 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;
/**
@@ -32,8 +32,7 @@ public class ConfigLoader {
Map<String, String> configContentMap = MappingConfigsLoader.loadConfigs("rdb");
configContentMap.forEach((fileName, content) -> {
MappingConfig config = YmlConfigBinder
.bindYmlToObj(null, content, MappingConfig.class, null, envProperties);
MappingConfig config = YamlUtils.ymlToObj(null, content, MappingConfig.class, null, envProperties);
if (config == null) {
return;
}

View File

@@ -1,25 +1,19 @@
package com.alibaba.otter.canal.client.adapter.rdb.monitor;
import com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter;
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
import com.alibaba.otter.canal.client.adapter.support.Util;
import com.alibaba.otter.canal.client.adapter.support.YamlUtils;
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.rdb.RdbAdapter;
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.rdb.config.MirrorDbConfig;
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
import com.alibaba.otter.canal.client.adapter.support.Util;
public class RdbConfigMonitor {
private static final Logger logger = LoggerFactory.getLogger(RdbConfigMonitor.class);
@@ -68,16 +62,14 @@ public class RdbConfigMonitor {
try {
// 加载新增的配置文件
String configContent = MappingConfigsLoader.loadConfig(adapterName + File.separator + file.getName());
MappingConfig config = YmlConfigBinder
.bindYmlToObj(null, configContent, MappingConfig.class, null, envProperties);
MappingConfig config = YamlUtils
.ymlToObj(null, configContent, MappingConfig.class, null, envProperties);
if (config == null) {
return;
}
config.validate();
if ((key == null && config.getOuterAdapterKey() == null)
|| (key != null && key.equals(config.getOuterAdapterKey()))) {
addConfigToCache(file, config);
boolean result = rdbAdapter.addConfig(file.getName(), config);
if (result) {
logger.info("Add a new rdb mapping config: {} to canal adapter", file.getName());
}
} catch (Exception e) {
@@ -98,22 +90,13 @@ public class RdbConfigMonitor {
onFileDelete(file);
return;
}
MappingConfig config = YmlConfigBinder
.bindYmlToObj(null, configContent, MappingConfig.class, null, envProperties);
MappingConfig config = YamlUtils
.ymlToObj(null, configContent, MappingConfig.class, null, envProperties);
if (config == null) {
return;
}
config.validate();
if ((key == null && config.getOuterAdapterKey() == null)
|| (key != null && key.equals(config.getOuterAdapterKey()))) {
if (rdbAdapter.getRdbMapping().containsKey(file.getName())) {
deleteConfigFromCache(file);
}
addConfigToCache(file, config);
} else {
// 不能修改outerAdapterKey
throw new RuntimeException("Outer adapter key not allowed modify");
}
rdbAdapter.updateConfig(file.getName(), config);
logger.info("Change a rdb mapping config: {} of canal adapter", file.getName());
}
} catch (Exception e) {
@@ -127,7 +110,7 @@ public class RdbConfigMonitor {
try {
if (rdbAdapter.getRdbMapping().containsKey(file.getName())) {
deleteConfigFromCache(file);
rdbAdapter.deleteConfig(file.getName());
logger.info("Delete a rdb mapping config: {} of canal adapter", file.getName());
}
@@ -135,47 +118,5 @@ public class RdbConfigMonitor {
logger.error(e.getMessage(), e);
}
}
private void addConfigToCache(File file, MappingConfig mappingConfig) {
if (mappingConfig == null || mappingConfig.getDbMapping() == null) {
return;
}
rdbAdapter.getRdbMapping().put(file.getName(), mappingConfig);
if (!mappingConfig.getDbMapping().getMirrorDb()) {
Map<String, MappingConfig> configMap = rdbAdapter.getMappingConfigCache()
.computeIfAbsent(StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-"
+ mappingConfig.getDbMapping().getTable(),
k1 -> new HashMap<>());
configMap.put(file.getName(), mappingConfig);
} else {
Map<String, MirrorDbConfig> mirrorDbConfigCache = rdbAdapter.getMirrorDbConfigCache();
mirrorDbConfigCache.put(StringUtils.trimToEmpty(mappingConfig.getDestination()) + "."
+ mappingConfig.getDbMapping().getDatabase(),
MirrorDbConfig.create(file.getName(), mappingConfig));
}
}
private void deleteConfigFromCache(File file) {
MappingConfig mappingConfig = rdbAdapter.getRdbMapping().remove(file.getName());
if (mappingConfig == null || mappingConfig.getDbMapping() == null) {
return;
}
if (!mappingConfig.getDbMapping().getMirrorDb()) {
for (Map<String, MappingConfig> configMap : rdbAdapter.getMappingConfigCache().values()) {
if (configMap != null) {
configMap.remove(file.getName());
}
}
} else {
rdbAdapter.getMirrorDbConfigCache().forEach((key, mirrorDbConfig) -> {
if (mirrorDbConfig.getFileName().equals(file.getName())) {
rdbAdapter.getMirrorDbConfigCache().remove(key);
}
});
}
}
}
}

View File

@@ -13,6 +13,7 @@ import java.util.concurrent.atomic.AtomicLong;
import javax.sql.DataSource;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig.DbMapping;
import com.alibaba.otter.canal.client.adapter.rdb.support.SyncUtil;
@@ -56,8 +57,11 @@ public class RdbEtlService extends AbstractEtlService {
DbMapping dbMapping = (DbMapping) mapping;
Map<String, String> columnsMap = new LinkedHashMap<>();
Map<String, Integer> columnType = new LinkedHashMap<>();
DruidDataSource dataSource = (DruidDataSource) srcDS;
Util.sqlRS(targetDS, "SELECT * FROM " + SyncUtil.getDbTableName(dbMapping) + " LIMIT 1 ", rs -> {
Util.sqlRS(targetDS,
"SELECT * FROM " + SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()) + " LIMIT 1 ",
rs -> {
try {
ResultSetMetaData rsd = rs.getMetaData();
@@ -83,7 +87,9 @@ public class RdbEtlService extends AbstractEtlService {
boolean completed = false;
StringBuilder insertSql = new StringBuilder();
insertSql.append("INSERT INTO ").append(SyncUtil.getDbTableName(dbMapping)).append(" (");
insertSql.append("INSERT INTO ")
.append(SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()))
.append(" (");
columnsMap
.forEach((targetColumnName, srcColumnName) -> insertSql.append(targetColumnName).append(","));
@@ -107,7 +113,7 @@ public class RdbEtlService extends AbstractEtlService {
// 删除数据
Map<String, Object> pkVal = new LinkedHashMap<>();
StringBuilder deleteSql = new StringBuilder(
"DELETE FROM " + SyncUtil.getDbTableName(dbMapping) + " WHERE ");
"DELETE FROM " + SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()) + " WHERE ");
appendCondition(dbMapping, deleteSql, pkVal, rs);
try (PreparedStatement pstmt2 = connTarget.prepareStatement(deleteSql.toString())) {
int k = 1;

View File

@@ -7,17 +7,17 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter.Feature;
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.rdb.config.MirrorDbConfig;
import com.alibaba.otter.canal.client.adapter.rdb.support.SingleDml;
import com.alibaba.otter.canal.client.adapter.rdb.support.SyncUtil;
import com.alibaba.otter.canal.client.adapter.support.Dml;
/**
@@ -31,10 +31,10 @@ public class RdbMirrorDbSyncService {
private static final Logger logger = LoggerFactory.getLogger(RdbMirrorDbSyncService.class);
private Map<String, MirrorDbConfig> mirrorDbConfigCache; // 镜像库配置
private DataSource dataSource;
private DruidDataSource dataSource;
private RdbSyncService rdbSyncService; // rdbSyncService代理
public RdbMirrorDbSyncService(Map<String, MirrorDbConfig> mirrorDbConfigCache, DataSource dataSource,
public RdbMirrorDbSyncService(Map<String, MirrorDbConfig> mirrorDbConfigCache, DruidDataSource dataSource,
Integer threads, Map<String, Map<String, Integer>> columnsTypeCache,
boolean skipDupException){
this.mirrorDbConfigCache = mirrorDbConfigCache;
@@ -45,7 +45,7 @@ public class RdbMirrorDbSyncService {
/**
* 批量同步方法
*
* @param dmls 批量 DML
* @param dmls 批量 DML,包含DDL
*/
public void sync(List<Dml> dmls) {
List<Dml> dmlList = new ArrayList<>();
@@ -66,9 +66,13 @@ public class RdbMirrorDbSyncService {
}
if (dml.getIsDdl() != null && dml.getIsDdl() && StringUtils.isNotEmpty(dml.getSql())) {
// 确保执行DDL前DML已执行完
syncDml(dmlList);
dmlList.clear();
// DDL
if (logger.isDebugEnabled()) {
logger.debug("DDL: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
logger.debug("DDL: {}", JSON.toJSONString(dml, Feature.WriteNulls));
}
executeDdl(mirrorDbConfig, dml);
rdbSyncService.getColumnsTypeCache().remove(destination + "." + database + "." + dml.getTable());
@@ -79,38 +83,32 @@ public class RdbMirrorDbSyncService {
dmlList.add(dml);
}
}
if (!dmlList.isEmpty()) {
rdbSyncService.sync(dmlList, dml -> {
MirrorDbConfig mirrorDbConfig = mirrorDbConfigCache.get(dml.getDestination() + "." + dml.getDatabase());
if (mirrorDbConfig == null) {
return false;
}
String table = dml.getTable();
MappingConfig config = mirrorDbConfig.getTableConfig().get(table);
syncDml(dmlList);
}
if (config == null) {
return false;
}
// 是否区分大小写
boolean caseInsensitive = config.getDbMapping().isCaseInsensitive();
if (config.getConcurrent()) {
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml, caseInsensitive);
singleDmls.forEach(singleDml -> {
int hash = rdbSyncService.pkHash(config.getDbMapping(), singleDml.getData());
RdbSyncService.SyncItem syncItem = new RdbSyncService.SyncItem(config, singleDml);
rdbSyncService.getDmlsPartition()[hash].add(syncItem);
});
} else {
int hash = 0;
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml, caseInsensitive);
singleDmls.forEach(singleDml -> {
RdbSyncService.SyncItem syncItem = new RdbSyncService.SyncItem(config, singleDml);
rdbSyncService.getDmlsPartition()[hash].add(syncItem);
});
}
return true;
});
/**
* 批量同步Dml
*
* @param dmlList Dml列表,不包含DDL
*/
private void syncDml(List<Dml> dmlList) {
if (dmlList == null || dmlList.isEmpty()) {
return;
}
rdbSyncService.sync(dmlList, dml -> {
MirrorDbConfig mirrorDbConfig = mirrorDbConfigCache.get(dml.getDestination() + "." + dml.getDatabase());
if (mirrorDbConfig == null) {
return false;
}
String table = dml.getTable();
MappingConfig config = mirrorDbConfig.getTableConfig().get(table);
if (config == null) {
return false;
}
rdbSyncService.appendDmlPartition(config, dml);
return true;
});
}
/**
@@ -153,7 +151,13 @@ public class RdbMirrorDbSyncService {
*/
private void executeDdl(MirrorDbConfig mirrorDbConfig, Dml ddl) {
try (Connection conn = dataSource.getConnection(); Statement statement = conn.createStatement()) {
statement.execute(ddl.getSql());
// 替换反引号
String sql = ddl.getSql();
String backtick = SyncUtil.getBacktickByDbType(dataSource.getDbType());
if (!"`".equals(backtick)) {
sql = sql.replaceAll("`", backtick);
}
statement.execute(sql);
// 移除对应配置
mirrorDbConfig.getTableConfig().remove(ddl.getTable());
if (logger.isTraceEnabled()) {

View File

@@ -3,6 +3,7 @@ package com.alibaba.otter.canal.client.adapter.rdb.service;
import java.sql.Connection;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
@@ -15,14 +16,13 @@ import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.function.Function;
import javax.sql.DataSource;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter.Feature;
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig.DbMapping;
import com.alibaba.otter.canal.client.adapter.rdb.support.BatchExecutor;
@@ -41,6 +41,7 @@ public class RdbSyncService {
private static final Logger logger = LoggerFactory.getLogger(RdbSyncService.class);
private DruidDataSource dataSource;
// 源库表字段类型缓存: instance.schema.table -> <columnName, jdbcType>
private Map<String, Map<String, Integer>> columnsTypeCache;
@@ -59,13 +60,14 @@ public class RdbSyncService {
return columnsTypeCache;
}
public RdbSyncService(DataSource dataSource, Integer threads, boolean skipDupException){
public RdbSyncService(DruidDataSource dataSource, Integer threads, boolean skipDupException){
this(dataSource, threads, new ConcurrentHashMap<>(), skipDupException);
}
@SuppressWarnings("unchecked")
public RdbSyncService(DataSource dataSource, Integer threads, Map<String, Map<String, Integer>> columnsTypeCache,
public RdbSyncService(DruidDataSource dataSource, Integer threads, Map<String, Map<String, Integer>> columnsTypeCache,
boolean skipDupException){
this.dataSource = dataSource;
this.columnsTypeCache = columnsTypeCache;
this.skipDupException = skipDupException;
try {
@@ -177,28 +179,38 @@ public class RdbSyncService {
}
for (MappingConfig config : configMap.values()) {
boolean caseInsensitive = config.getDbMapping().isCaseInsensitive();
if (config.getConcurrent()) {
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml, caseInsensitive);
singleDmls.forEach(singleDml -> {
int hash = pkHash(config.getDbMapping(), singleDml.getData());
SyncItem syncItem = new SyncItem(config, singleDml);
dmlsPartition[hash].add(syncItem);
});
} else {
int hash = 0;
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml, caseInsensitive);
singleDmls.forEach(singleDml -> {
SyncItem syncItem = new SyncItem(config, singleDml);
dmlsPartition[hash].add(syncItem);
});
}
appendDmlPartition(config, dml);
}
return true;
}
} );
}
/**
* 将Dml加入 {@link #dmlsPartition}
*
* @param config 表映射配置
* @param dml Dml对象
*/
public void appendDmlPartition(MappingConfig config, Dml dml) {
boolean caseInsensitive = config.getDbMapping().isCaseInsensitive();
if (config.getConcurrent()) {
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml, caseInsensitive);
singleDmls.forEach(singleDml -> {
int hash = pkHash(config.getDbMapping(), singleDml.getData());
SyncItem syncItem = new SyncItem(config, singleDml);
dmlsPartition[hash].add(syncItem);
});
} else {
int hash = 0;
List<SingleDml> singleDmls = SingleDml.dml2SingleDmls(dml, caseInsensitive);
singleDmls.forEach(singleDml -> {
SyncItem syncItem = new SyncItem(config, singleDml);
dmlsPartition[hash].add(syncItem);
});
}
}
/**
* 单条 dml 同步
*
@@ -220,7 +232,7 @@ public class RdbSyncService {
truncate(batchExecutor, config);
}
if (logger.isDebugEnabled()) {
logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
logger.debug("DML: {}", JSON.toJSONString(dml, Feature.WriteNulls));
}
} catch (SQLException e) {
throw new RuntimeException(e);
@@ -241,15 +253,15 @@ public class RdbSyncService {
}
DbMapping dbMapping = config.getDbMapping();
String backtick = SyncUtil.getBacktickByDbType(dataSource.getDbType());
Map<String, String> columnsMap = SyncUtil.getColumnsMap(dbMapping, data);
StringBuilder insertSql = new StringBuilder();
insertSql.append("INSERT INTO ").append(SyncUtil.getDbTableName(dbMapping)).append(" (");
insertSql.append("INSERT INTO ").append(SyncUtil.getDbTableName(dbMapping, dataSource.getDbType())).append(" (");
columnsMap.forEach((targetColumnName, srcColumnName) -> insertSql.append("`")
columnsMap.forEach((targetColumnName, srcColumnName) -> insertSql.append(backtick)
.append(targetColumnName)
.append("`")
.append(backtick)
.append(","));
int len = insertSql.length();
insertSql.delete(len - 1, len).append(") VALUES (");
@@ -313,13 +325,13 @@ public class RdbSyncService {
}
DbMapping dbMapping = config.getDbMapping();
String backtick = SyncUtil.getBacktickByDbType(dataSource.getDbType());
Map<String, String> columnsMap = SyncUtil.getColumnsMap(dbMapping, data);
Map<String, Integer> ctype = getTargetColumnType(batchExecutor.getConn(), config);
StringBuilder updateSql = new StringBuilder();
updateSql.append("UPDATE ").append(SyncUtil.getDbTableName(dbMapping)).append(" SET ");
updateSql.append("UPDATE ").append(SyncUtil.getDbTableName(dbMapping, dataSource.getDbType())).append(" SET ");
List<Map<String, ?>> values = new ArrayList<>();
boolean hasMatched = false;
for (String srcColumnName : old.keySet()) {
@@ -332,7 +344,7 @@ public class RdbSyncService {
if (!targetColumnNames.isEmpty()) {
hasMatched = true;
for (String targetColumnName : targetColumnNames) {
updateSql.append("`").append(targetColumnName).append("`").append("=?, ");
updateSql.append(backtick).append(targetColumnName).append(backtick).append("=?, ");
Integer type = ctype.get(Util.cleanColumn(targetColumnName).toLowerCase());
if (type == null) {
throw new RuntimeException("Target column: " + targetColumnName + " not matched");
@@ -369,11 +381,10 @@ public class RdbSyncService {
}
DbMapping dbMapping = config.getDbMapping();
Map<String, Integer> ctype = getTargetColumnType(batchExecutor.getConn(), config);
StringBuilder sql = new StringBuilder();
sql.append("DELETE FROM ").append(SyncUtil.getDbTableName(dbMapping)).append(" WHERE ");
sql.append("DELETE FROM ").append(SyncUtil.getDbTableName(dbMapping, dataSource.getDbType())).append(" WHERE ");
List<Map<String, ?>> values = new ArrayList<>();
// 拼接主键
@@ -392,7 +403,7 @@ public class RdbSyncService {
private void truncate(BatchExecutor batchExecutor, MappingConfig config) throws SQLException {
DbMapping dbMapping = config.getDbMapping();
StringBuilder sql = new StringBuilder();
sql.append("TRUNCATE TABLE ").append(SyncUtil.getDbTableName(dbMapping));
sql.append("TRUNCATE TABLE ").append(SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()));
batchExecutor.execute(sql.toString(), new ArrayList<>());
if (logger.isTraceEnabled()) {
logger.trace("Truncate target table, sql: {}", sql);
@@ -416,13 +427,18 @@ public class RdbSyncService {
if (columnType == null) {
columnType = new LinkedHashMap<>();
final Map<String, Integer> columnTypeTmp = columnType;
String sql = "SELECT * FROM " + SyncUtil.getDbTableName(dbMapping) + " WHERE 1=2";
String sql = "SELECT * FROM " + SyncUtil.getDbTableName(dbMapping, dataSource.getDbType()) + " WHERE 1=2";
Util.sqlRS(conn, sql, rs -> {
try {
ResultSetMetaData rsd = rs.getMetaData();
int columnCount = rsd.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
columnTypeTmp.put(rsd.getColumnName(i).toLowerCase(), rsd.getColumnType(i));
int colType = rsd.getColumnType(i);
// 修复year类型作为date处理时的data truncated问题
if ("YEAR".equals(rsd.getColumnTypeName(i))) {
colType = Types.VARCHAR;
}
columnTypeTmp.put(rsd.getColumnName(i).toLowerCase(), colType);
}
columnsTypeCache.put(cacheKey, columnTypeTmp);
} catch (SQLException e) {
@@ -445,6 +461,8 @@ public class RdbSyncService {
private void appendCondition(MappingConfig.DbMapping dbMapping, StringBuilder sql, Map<String, Integer> ctype,
List<Map<String, ?>> values, Map<String, Object> d, Map<String, Object> o) {
String backtick = SyncUtil.getBacktickByDbType(dataSource.getDbType());
// 拼接主键
for (Map.Entry<String, String> entry : dbMapping.getTargetPk().entrySet()) {
String targetColumnName = entry.getKey();
@@ -452,7 +470,7 @@ public class RdbSyncService {
if (srcColumnName == null) {
srcColumnName = Util.cleanColumn(targetColumnName);
}
sql.append("`").append(targetColumnName).append("`").append("=? AND ");
sql.append(backtick).append(targetColumnName).append(backtick).append("=? AND ");
Integer type = ctype.get(Util.cleanColumn(targetColumnName).toLowerCase());
if (type == null) {
throw new RuntimeException("Target column: " + targetColumnName + " not matched");

View File

@@ -1,5 +1,6 @@
package com.alibaba.otter.canal.client.adapter.rdb.support;
import com.alibaba.druid.DbType;
import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig;
import com.alibaba.otter.canal.client.adapter.support.Util;
import org.apache.commons.lang.StringUtils;
@@ -255,12 +256,36 @@ public class SyncUtil {
}
}
public static String getDbTableName(MappingConfig.DbMapping dbMapping) {
public static String getDbTableName(MappingConfig.DbMapping dbMapping, String dbType) {
String result = "";
String backtick = getBacktickByDbType(dbType);
if (StringUtils.isNotEmpty(dbMapping.getTargetDb())) {
result += ("`" + dbMapping.getTargetDb() + "`.");
result += (backtick + dbMapping.getTargetDb() + backtick + ".");
}
result += ("`" + dbMapping.getTargetTable() + "`");
result += (backtick + dbMapping.getTargetTable() + backtick);
return result;
}
/**
* 根据DbType返回反引号或空字符串
*
* @param dbTypeName DbType名称
* @return 反引号或空字符串
*/
public static String getBacktickByDbType(String dbTypeName) {
DbType dbType = DbType.of(dbTypeName);
if (dbType == null) {
dbType = DbType.other;
}
// 只有当dbType为MySQL/MariaDB或OceanBase时返回反引号
switch (dbType) {
case mysql:
case mariadb:
case oceanbase:
return "`";
default:
return "";
}
}
}

View File

@@ -3,7 +3,7 @@
<parent>
<artifactId>canal.client-adapter</artifactId>
<groupId>com.alibaba.otter</groupId>
<version>1.1.6-SNAPSHOT</version>
<version>1.1.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -1,6 +1,8 @@
package com.alibaba.otter.canal.client.adapter.tablestore;
import com.alibaba.otter.canal.client.adapter.support.FileName2KeyMapping;
import com.alibaba.otter.canal.client.adapter.support.Util;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
@@ -55,62 +57,14 @@ public class TablestoreAdapter implements OuterAdapter {
this.configuration = configuration;
Map<String, MappingConfig> tablestoreMappingTmp = ConfigLoader.load(envProperties);
// 过滤不匹配的key的配置
tablestoreMappingTmp.forEach((key, mappingConfig) -> {
if ((mappingConfig.getOuterAdapterKey() == null && configuration.getKey() == null)
|| (mappingConfig.getOuterAdapterKey() != null && mappingConfig.getOuterAdapterKey()
.equalsIgnoreCase(configuration.getKey()))) {
tablestoreMapping.put(key, mappingConfig);
mappingConfig.getDbMapping().init(mappingConfig);
}
tablestoreMappingTmp.forEach((key, config) -> {
addConfig(key, config);
});
if (tablestoreMapping.isEmpty()) {
throw new RuntimeException("No tablestore adapter found for config key: " + configuration.getKey());
}
Map<String, String> properties = configuration.getProperties();
for (Map.Entry<String, MappingConfig> entry : tablestoreMapping.entrySet()) {
String configName = entry.getKey();
MappingConfig mappingConfig = entry.getValue();
String key;
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "-"
+ StringUtils.trimToEmpty(mappingConfig.getGroupId()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-" + mappingConfig.getDbMapping().getTable();
} else {
key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-" + mappingConfig.getDbMapping().getTable();
}
Map<String, MappingConfig> configMap = mappingConfigCache.computeIfAbsent(key,
k1 -> new ConcurrentHashMap<>());
configMap.put(configName, mappingConfig);
// 构建对应的 TableStoreWriter
ServiceCredentials credentials = new DefaultCredentials(
properties.get(PropertyConstants.TABLESTORE_ACCESSSECRETID),
properties.get(PropertyConstants.TABLESTORE_ACCESSSECRETKEY)
);
WriterConfig config = getWriterConfig(mappingConfig);
TableStoreWriter writer = new DefaultTableStoreWriter(
properties.get(PropertyConstants.TABLESTORE_ENDPOINT),
credentials,
properties.get(PropertyConstants.TABLESTORE_INSTANCENAME),
mappingConfig.getDbMapping().getTargetTable(),
config,
null
);
Map<String, TableStoreWriter> config2writerMap = writerCache.computeIfAbsent(key,
k1 -> new ConcurrentHashMap<>());
config2writerMap.put(configName, writer);
}
tablestoreSyncService = new TablestoreSyncService();
}
@@ -315,4 +269,84 @@ public class TablestoreAdapter implements OuterAdapter {
}
}
}
private void addSyncConfigToCache(String configName, MappingConfig mappingConfig) {
Map<String, String> properties = configuration.getProperties();
String key;
if (envProperties != null && !"tcp".equalsIgnoreCase(envProperties.getProperty("canal.conf.mode"))) {
key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "-"
+ StringUtils.trimToEmpty(mappingConfig.getGroupId()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-" + mappingConfig.getDbMapping().getTable();
} else {
key = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "_"
+ mappingConfig.getDbMapping().getDatabase() + "-" + mappingConfig.getDbMapping().getTable();
}
Map<String, MappingConfig> configMap = mappingConfigCache.computeIfAbsent(key,
k1 -> new ConcurrentHashMap<>());
configMap.put(configName, mappingConfig);
// 构建对应的 TableStoreWriter
ServiceCredentials credentials = new DefaultCredentials(
properties.get(PropertyConstants.TABLESTORE_ACCESSSECRETID),
properties.get(PropertyConstants.TABLESTORE_ACCESSSECRETKEY)
);
WriterConfig config = getWriterConfig(mappingConfig);
TableStoreWriter writer = new DefaultTableStoreWriter(
properties.get(PropertyConstants.TABLESTORE_ENDPOINT),
credentials,
properties.get(PropertyConstants.TABLESTORE_INSTANCENAME),
mappingConfig.getDbMapping().getTargetTable(),
config,
null
);
Map<String, TableStoreWriter> config2writerMap = writerCache.computeIfAbsent(key,
k1 -> new ConcurrentHashMap<>());
config2writerMap.put(configName, writer);
}
public boolean addConfig(String fileName, MappingConfig config) {
if (match(config)) {
tablestoreMapping.put(fileName, config);
addSyncConfigToCache(fileName, config);
FileName2KeyMapping.register(getClass().getAnnotation(SPI.class).value(), fileName,
configuration.getKey());
return true;
}
return false;
}
public void updateConfig(String fileName, MappingConfig config) {
if (config.getOuterAdapterKey() != null && !config.getOuterAdapterKey()
.equals(configuration.getKey())) {
// 理论上不允许改这个 因为本身就是通过这个关联起Adapter和Config的
throw new RuntimeException("not allow to change outAdapterKey");
}
tablestoreMapping.put(fileName, config);
addSyncConfigToCache(fileName, config);
}
public void deleteConfig(String fileName) {
tablestoreMapping.remove(fileName);
for (Map<String, MappingConfig> configMap : mappingConfigCache.values()) {
if (configMap != null) {
configMap.remove(fileName);
}
}
FileName2KeyMapping.unregister(getClass().getAnnotation(SPI.class).value(), fileName);
}
private boolean match(MappingConfig config) {
boolean sameMatch = config.getOuterAdapterKey() != null && config.getOuterAdapterKey()
.equalsIgnoreCase(configuration.getKey());
boolean prefixMatch = config.getOuterAdapterKey() == null && configuration.getKey()
.startsWith(StringUtils
.join(new String[]{Util.AUTO_GENERATED_PREFIX, config.getDestination(),
config.getGroupId()}, '-'));
return sameMatch || prefixMatch;
}
}

View File

@@ -1,7 +1,7 @@
package com.alibaba.otter.canal.client.adapter.tablestore.config;
import com.alibaba.otter.canal.client.adapter.config.YmlConfigBinder;
import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader;
import com.alibaba.otter.canal.client.adapter.support.YamlUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
@@ -32,8 +32,7 @@ public class ConfigLoader {
Map<String, String> configContentMap = MappingConfigsLoader.loadConfigs("tablestore");
configContentMap.forEach((fileName, content) -> {
MappingConfig config = YmlConfigBinder
.bindYmlToObj(null, content, MappingConfig.class, null, envProperties);
MappingConfig config = YamlUtils.ymlToObj(null, content, MappingConfig.class, null, envProperties);
if (config == null) {
return;
}
@@ -45,7 +44,8 @@ public class ConfigLoader {
result.put(fileName, config);
});
logger.info("## Tablestore mapping config loaded:" + StringUtils.collectionToCommaDelimitedString(result.keySet()));
logger.info(
"## Tablestore mapping config loaded:" + StringUtils.collectionToCommaDelimitedString(result.keySet()));
return result;
}
}

View File

@@ -6,8 +6,8 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONWriter.Feature;
import com.alibaba.otter.canal.client.adapter.tablestore.enums.TablestoreFieldType;
import com.alibaba.otter.canal.client.adapter.tablestore.support.SyncUtil;
import com.alicloud.openservices.tablestore.TableStoreWriter;
@@ -59,7 +59,7 @@ public class TablestoreSyncService {
Future<WriterResult> future = writer.addRowChangeWithFuture(rowChanges);
if (logger.isDebugEnabled()) {
logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue));
logger.debug("DML: {}", JSON.toJSONString(dml, Feature.WriteNulls));
}
return future;
}

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>com.alibaba.otter</groupId>
<artifactId>canal</artifactId>
<version>1.1.6-SNAPSHOT</version>
<version>1.1.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>com.alibaba.otter</groupId>
@@ -50,10 +50,6 @@
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
@@ -138,6 +134,11 @@
<artifactId>pulsar-client</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.pulsar</groupId>
<artifactId>pulsar-client-admin</artifactId>
<scope>provided</scope>
</dependency>
<!-- junit -->
<dependency>

View File

@@ -39,6 +39,7 @@ public class ClusterCanalConnector implements CanalConnector {
this.accessStrategy = accessStrategy;
}
@Override
public void connect() throws CanalClientException {
while (currentConnector == null) {
int times = 0;
@@ -85,10 +86,12 @@ public class ClusterCanalConnector implements CanalConnector {
}
}
@Override
public boolean checkValid() {
return currentConnector != null && currentConnector.checkValid();
}
@Override
public void disconnect() throws CanalClientException {
if (currentConnector != null) {
currentConnector.disconnect();
@@ -96,10 +99,12 @@ public class ClusterCanalConnector implements CanalConnector {
}
}
@Override
public void subscribe() throws CanalClientException {
subscribe(""); // 传递空字符即可
}
@Override
public void subscribe(String filter) throws CanalClientException {
int times = 0;
while (times < retryTimes) {
@@ -125,6 +130,7 @@ public class ClusterCanalConnector implements CanalConnector {
throw new CanalClientException("failed to subscribe after " + times + " times retry.");
}
@Override
public void unsubscribe() throws CanalClientException {
int times = 0;
while (times < retryTimes) {
@@ -142,6 +148,7 @@ public class ClusterCanalConnector implements CanalConnector {
throw new CanalClientException("failed to unsubscribe after " + times + " times retry.");
}
@Override
public Message get(int batchSize) throws CanalClientException {
int times = 0;
while (times < retryTimes) {
@@ -159,6 +166,7 @@ public class ClusterCanalConnector implements CanalConnector {
throw new CanalClientException("failed to fetch the data after " + times + " times retry");
}
@Override
public Message get(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException {
int times = 0;
while (times < retryTimes) {
@@ -176,6 +184,7 @@ public class ClusterCanalConnector implements CanalConnector {
throw new CanalClientException("failed to fetch the data after " + times + " times retry");
}
@Override
public Message getWithoutAck(int batchSize) throws CanalClientException {
int times = 0;
while (times < retryTimes) {
@@ -193,6 +202,7 @@ public class ClusterCanalConnector implements CanalConnector {
throw new CanalClientException("failed to fetch the data after " + times + " times retry");
}
@Override
public Message getWithoutAck(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException {
int times = 0;
while (times < retryTimes) {
@@ -210,6 +220,7 @@ public class ClusterCanalConnector implements CanalConnector {
throw new CanalClientException("failed to fetch the data after " + times + " times retry");
}
@Override
public void rollback(long batchId) throws CanalClientException {
int times = 0;
while (times < retryTimes) {
@@ -227,6 +238,7 @@ public class ClusterCanalConnector implements CanalConnector {
throw new CanalClientException("failed to rollback after " + times + " times retry");
}
@Override
public void rollback() throws CanalClientException {
int times = 0;
while (times < retryTimes) {
@@ -245,6 +257,7 @@ public class ClusterCanalConnector implements CanalConnector {
throw new CanalClientException("failed to rollback after " + times + " times retry");
}
@Override
public void ack(long batchId) throws CanalClientException {
int times = 0;
while (times < retryTimes) {

View File

@@ -63,6 +63,7 @@ public class ClusterNodeAccessStrategy implements CanalNodeAccessStrategy {
return nextNode();
}
@Override
public SocketAddress nextNode() {
if (runningAddress != null) {// 如果服务已经启动,直接选择当前正在工作的节点
return runningAddress;
@@ -81,7 +82,7 @@ public class ClusterNodeAccessStrategy implements CanalNodeAccessStrategy {
for (String address : currentChilds) {
String[] strs = StringUtils.split(address, ":");
if (strs != null && strs.length == 2) {
addresses.add(new InetSocketAddress(strs[0], Integer.valueOf(strs[1])));
addresses.add(new InetSocketAddress(strs[0], Integer.parseInt(strs[1])));
}
}
@@ -98,7 +99,7 @@ public class ClusterNodeAccessStrategy implements CanalNodeAccessStrategy {
ServerRunningData runningData = JsonUtils.unmarshalFromByte((byte[]) data, ServerRunningData.class);
String[] strs = StringUtils.split(runningData.getAddress(), ':');
if (strs.length == 2) {
runningAddress = new InetSocketAddress(strs[0], Integer.valueOf(strs[1]));
runningAddress = new InetSocketAddress(strs[0], Integer.parseInt(strs[1]));
}
}

View File

@@ -98,6 +98,7 @@ public class SimpleCanalConnector implements CanalConnector {
this.clientIdentity = new ClientIdentity(destination, (short) 1001);
}
@Override
public void connect() throws CanalClientException {
if (connected) {
return;
@@ -124,6 +125,7 @@ public class SimpleCanalConnector implements CanalConnector {
connected = true;
}
@Override
public void disconnect() throws CanalClientException {
if (rollbackOnDisConnect && channel.isConnected()) {
rollback();
@@ -222,10 +224,12 @@ public class SimpleCanalConnector implements CanalConnector {
}
}
@Override
public void subscribe() throws CanalClientException {
subscribe(""); // 传递空字符即可
}
@Override
public void subscribe(String filter) throws CanalClientException {
waitClientRunning();
if (!running) {
@@ -255,6 +259,7 @@ public class SimpleCanalConnector implements CanalConnector {
}
}
@Override
public void unsubscribe() throws CanalClientException {
waitClientRunning();
if (!running) {
@@ -281,20 +286,24 @@ public class SimpleCanalConnector implements CanalConnector {
}
}
@Override
public Message get(int batchSize) throws CanalClientException {
return get(batchSize, null, null);
}
@Override
public Message get(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException {
Message message = getWithoutAck(batchSize, timeout, unit);
ack(message.getId());
return message;
}
@Override
public Message getWithoutAck(int batchSize) throws CanalClientException {
return getWithoutAck(batchSize, null, null);
}
@Override
public Message getWithoutAck(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException {
waitClientRunning();
if (!running) {
@@ -331,6 +340,7 @@ public class SimpleCanalConnector implements CanalConnector {
return CanalMessageDeserializer.deserializer(data, lazyParseEntry);
}
@Override
public void ack(long batchId) throws CanalClientException {
waitClientRunning();
if (!running) {
@@ -352,6 +362,7 @@ public class SimpleCanalConnector implements CanalConnector {
}
}
@Override
public void rollback(long batchId) throws CanalClientException {
waitClientRunning();
ClientRollback ca = ClientRollback.newBuilder()
@@ -370,9 +381,10 @@ public class SimpleCanalConnector implements CanalConnector {
}
}
@Override
public void rollback() throws CanalClientException {
waitClientRunning();
rollback(0);// 0代笔未设置
rollback(0); // 0 代表未设置
}
// ==================== helper method ====================

View File

@@ -24,6 +24,7 @@ public class SimpleNodeAccessStrategy implements CanalNodeAccessStrategy {
this.nodes.addAll(nodes);
}
@Override
public SocketAddress nextNode() {
try {
return nodes.get(index);

View File

@@ -10,7 +10,7 @@ import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson2.JSON;
import com.alibaba.otter.canal.client.CanalMQConnector;
import com.alibaba.otter.canal.client.impl.SimpleCanalConnector;
import com.alibaba.otter.canal.protocol.FlatMessage;

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