families) {
+ this.families = families;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ HbaseOrm hbaseOrm = (HbaseOrm) o;
+
+ if (table != null ? !table.equals(hbaseOrm.table) : hbaseOrm.table != null) return false;
+ return hbaseTable != null ? hbaseTable.equals(hbaseOrm.hbaseTable) : hbaseOrm.hbaseTable == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = table != null ? table.hashCode() : 0;
+ result = 31 * result + (hbaseTable != null ? hbaseTable.hashCode() : 0);
+ return result;
+ }
+ }
+}
diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java
new file mode 100644
index 00000000..f00e6282
--- /dev/null
+++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java
@@ -0,0 +1,149 @@
+package com.alibaba.otter.canal.client.adapter.hbase.config;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.yaml.snakeyaml.Yaml;
+
+/**
+ * HBase表映射配置加载器
+ *
+ * 配置统一从hbase-mapping/configs.conf文件作为入口, 该文件包含所有表映射配置的名称或者文件名列表。
+ * 每个对应的表配置可以yml配置文件或者以database.table为配置名的简化形式
+ *
+ *
+ * @author machengyuan 2018-8-21 下午06:45:49
+ * @version 1.0.0
+ */
+public class MappingConfigLoader {
+
+ private static Logger logger = LoggerFactory.getLogger(MappingConfigLoader.class);
+
+ private static final String BASE_PATH = "hbase-mapping/";
+
+ /**
+ * 加载HBase表映射配置
+ *
+ * @return 配置名/配置文件名--对象
+ */
+ public static Map load() {
+ logger.info("## Start loading mapping config ... ");
+ String mappingConfigContent = readConfigContent(BASE_PATH + "configs.conf");
+
+ Map result = new LinkedHashMap<>();
+
+ String[] configLines = mappingConfigContent.split("\n");
+ for (String c : configLines) {
+ if (c == null) {
+ continue;
+ }
+ c = c.trim();
+ if (c.startsWith("#")) {
+ continue;
+ }
+
+ MappingConfig config;
+ String configContent = null;
+
+ if (c.endsWith(".yml")) {
+ configContent = readConfigContent(BASE_PATH + "/" + c);
+ }
+
+ // 简单配置database.table@datasourcekey?rowKey=key1,key2
+ if (StringUtils.isEmpty(configContent)) {
+ String[] mapping = c.split("\\?");
+ String params = mapping.length == 2 ? mapping[1] : null;
+ String rowKey = null;
+ String srcMeta = mapping[0];
+ //
+ if (params != null) {
+ for (String entry : params.split("&")) {
+ if ("rowKey".equals(entry.split("=")[0])) {
+ rowKey = entry.split("=")[1];
+ }
+ }
+ }
+ String dsKey = srcMeta.split("@").length == 2 ? srcMeta.split("@")[1] : null;
+ String[] dbTable;
+ if (dsKey == null) {
+ dbTable = srcMeta.split("\\.");
+ } else {
+ dbTable = srcMeta.split("@")[0].split("\\.");
+ }
+
+ if (dbTable.length == 2) {
+ config = new MappingConfig();
+
+ MappingConfig.HbaseOrm hbaseOrm = new MappingConfig.HbaseOrm();
+ hbaseOrm.setHbaseTable(dbTable[0].toUpperCase() + "." + dbTable[1].toUpperCase());
+ hbaseOrm.setAutoCreateTable(true);
+ hbaseOrm.setDatabase(dbTable[0]);
+ hbaseOrm.setTable(dbTable[1]);
+ hbaseOrm.setMode(MappingConfig.Mode.STRING);
+ hbaseOrm.setRowKey(rowKey);
+ // 有定义rowKey
+ if (rowKey != null) {
+ MappingConfig.ColumnItem columnItem = new MappingConfig.ColumnItem();
+ columnItem.setRowKey(true);
+ columnItem.setColumn(rowKey);
+ hbaseOrm.setRowKeyColumn(columnItem);
+ }
+ config.setHbaseOrm(hbaseOrm);
+
+ } else {
+ throw new RuntimeException(String.format("配置项[%s]内容为空, 或格式不符合database.table", c));
+ }
+
+ } else { // 配置文件配置
+ config = new Yaml().loadAs(configContent, MappingConfig.class);
+ }
+
+ try {
+ config.validate();
+ } catch (Exception e) {
+ throw new RuntimeException("ERROR Config: " + c + " " + e.getMessage(), e);
+ }
+ result.put(c, config);
+ }
+
+ logger.info("## Mapping config loaded");
+ return result;
+ }
+
+ public static String readConfigContent(String config) {
+ InputStream in = null;
+ try {
+ // 先取本地文件,再取类路径
+ File configFile = new File("config/" + config);
+ if (configFile.exists()) {
+ in = new FileInputStream(configFile);
+ } else {
+ in = MappingConfigLoader.class.getClassLoader().getResourceAsStream(config);
+ }
+ if (in == null) {
+ throw new RuntimeException("Config file not found.");
+ }
+
+ byte[] bytes = new byte[in.available()];
+ in.read(bytes);
+ return new String(bytes, "UTF-8");
+ } catch (IOException e) {
+ throw new RuntimeException("Read ds-config.yml or hbase-mappings.conf error. ", e);
+ } finally {
+ try {
+ if (in != null) {
+ in.close();
+ }
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+}
diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java
new file mode 100644
index 00000000..13f6a3ac
--- /dev/null
+++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java
@@ -0,0 +1,418 @@
+package com.alibaba.otter.canal.client.adapter.hbase.service;
+
+import java.util.*;
+
+import org.apache.hadoop.hbase.client.Connection;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig;
+import com.alibaba.otter.canal.client.adapter.hbase.support.*;
+import com.alibaba.otter.canal.client.adapter.support.Dml;
+
+/**
+ * HBase同步操作业务
+ *
+ * @author machengyuan 2018-8-21 下午06:45:49
+ * @version 1.0.0
+ */
+public class HbaseSyncService {
+
+ private Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ private HbaseTemplate hbaseTemplate; // HBase操作模板
+
+ public HbaseSyncService(Connection conn){
+ hbaseTemplate = new HbaseTemplate(conn);
+ }
+
+ public void sync(MappingConfig config, Dml dml) {
+ try {
+ if (config != null) {
+ String type = dml.getType();
+ if (type != null && type.equalsIgnoreCase("INSERT")) {
+ insert(config, dml);
+ } else if (type != null && type.equalsIgnoreCase("UPDATE")) {
+ update(config, dml);
+ } else if (type != null && type.equalsIgnoreCase("DELETE")) {
+ delete(config, dml);
+ }
+ if (logger.isDebugEnabled()) {
+ String res = dml.toString();
+ logger.debug(res);
+ }
+ }
+ } catch (Exception e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 插入操作
+ *
+ * @param config 配置项
+ * @param dml DML数据
+ */
+ private void insert(MappingConfig config, Dml dml) {
+ List