Explorar el Código

merge listener module

AE86 hace 1 año
padre
commit
2e0f88f4a8
Se han modificado 100 ficheros con 2348 adiciones y 2315 borrados
  1. 1 1
      dbsyncer-biz/src/main/java/org/dbsyncer/biz/checker/impl/connector/FileConfigChecker.java
  2. 2 2
      dbsyncer-biz/src/main/java/org/dbsyncer/biz/checker/impl/mapping/LogConfigChecker.java
  3. 2 2
      dbsyncer-biz/src/main/java/org/dbsyncer/biz/checker/impl/mapping/MappingChecker.java
  4. 2 2
      dbsyncer-biz/src/main/java/org/dbsyncer/biz/checker/impl/mapping/TimingConfigChecker.java
  5. 1 1
      dbsyncer-biz/src/main/java/org/dbsyncer/biz/impl/ConditionServiceImpl.java
  6. 1 1
      dbsyncer-biz/src/main/java/org/dbsyncer/biz/impl/DataSyncServiceImpl.java
  7. 1 1
      dbsyncer-biz/src/main/java/org/dbsyncer/biz/vo/ConditionVo.java
  8. 10 0
      dbsyncer-connector/pom.xml
  9. 5 4
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/AbstractDatabaseListener.java
  10. 9 7
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/AbstractListener.java
  11. 19 12
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/ConnectorFactory.java
  12. 2 2
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/config/ListenerConfig.java
  13. 6 6
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/enums/QuartzFilterEnum.java
  14. 1 1
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/enums/TableOperationEnum.java
  15. 10 0
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/es/ESConnector.java
  16. 8 5
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/es/ESQuartzListener.java
  17. 1 1
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/file/BufferedRandomAccessFile.java
  18. 10 1
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/file/FileConnector.java
  19. 0 1
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/file/FileConnectorInstance.java
  20. 7 10
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/file/FileListener.java
  21. 1 1
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/file/FileSchema.java
  22. 6 0
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/kafka/KafkaConnector.java
  23. 116 116
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/mysql/BinaryLogClient.java
  24. 836 836
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/mysql/BinaryLogRemoteClient.java
  25. 3 3
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/mysql/DqlMySQLListener.java
  26. 16 1
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/mysql/MySQLConnector.java
  27. 358 341
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/mysql/MySQLListener.java
  28. 1 1
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/mysql/deserializer/DatetimeV2Deserialize.java
  29. 1 1
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/mysql/deserializer/DeleteDeserializer.java
  30. 1 1
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/mysql/deserializer/JsonBinaryDeserialize.java
  31. 1 1
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/mysql/deserializer/UpdateDeserializer.java
  32. 1 1
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/mysql/deserializer/WriteDeserializer.java
  33. 28 28
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/oracle/DCNEvent.java
  34. 3 3
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/oracle/DqlOracleListener.java
  35. 15 0
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/oracle/OracleConnector.java
  36. 6 6
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/oracle/OracleListener.java
  37. 8 8
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/oracle/dcn/DBChangeNotification.java
  38. 18 18
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/oracle/dcn/RowEventListener.java
  39. 3 3
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/postgresql/AbstractMessageDecoder.java
  40. 3 3
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/postgresql/DqlPostgreSQLListener.java
  41. 2 2
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/postgresql/MessageDecoder.java
  42. 15 0
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/postgresql/PostgreSQLConnector.java
  43. 14 14
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/postgresql/PostgreSQLListener.java
  44. 23 13
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/postgresql/column/PgColumnValue.java
  45. 8 8
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/postgresql/decoder/PgOutputMessageDecoder.java
  46. 5 5
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/postgresql/decoder/TestDecodingMessageDecoder.java
  47. 6 6
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/postgresql/enums/MessageDecoderEnum.java
  48. 41 41
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/postgresql/enums/MessageTypeEnum.java
  49. 4 4
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/quartz/AbstractQuartzListener.java
  50. 3 3
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/quartz/DatabaseQuartzListener.java
  51. 1 1
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/quartz/Point.java
  52. 1 1
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/quartz/QuartzFilter.java
  53. 1 1
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/quartz/TableGroupQuartzCommand.java
  54. 2 2
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/quartz/filter/DateFilter.java
  55. 2 2
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/quartz/filter/TimestampFilter.java
  56. 2 2
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/quartz/filter/YesDateFilter.java
  57. 2 2
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/quartz/filter/YesTimestampFilter.java
  58. 16 0
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/sql/DQLMySQLConnector.java
  59. 16 0
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/sql/DQLOracleConnector.java
  60. 16 0
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/sql/DQLPostgreSQLConnector.java
  61. 16 0
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/sql/DQLSqlServerConnector.java
  62. 1 1
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/sqlserver/CDCEvent.java
  63. 3 3
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/sqlserver/DqlSqlServerListener.java
  64. 1 1
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/sqlserver/Lsn.java
  65. 8 8
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/sqlserver/LsnPuller.java
  66. 2 4
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/sqlserver/SqlServerChangeTable.java
  67. 19 20
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/sqlserver/SqlServerConnector.java
  68. 9 9
      dbsyncer-connector/src/main/java/org/dbsyncer/connector/sqlserver/SqlServerListener.java
  69. 2 2
      dbsyncer-connector/src/main/test/BinaryLogRemoteClientTest.java
  70. 314 314
      dbsyncer-connector/src/main/test/ChangeDataCaptureTest.java
  71. 100 100
      dbsyncer-connector/src/main/test/DBChangeNotificationTest.java
  72. 0 0
      dbsyncer-connector/src/main/test/ESClientTest.java
  73. 0 0
      dbsyncer-connector/src/main/test/FileWatchTest.java
  74. 0 0
      dbsyncer-connector/src/main/test/KafkaClientTest.java
  75. 85 85
      dbsyncer-connector/src/main/test/LinkedBlockingQueueTest.java
  76. 0 0
      dbsyncer-connector/src/main/test/PGReplicationTest.java
  77. 0 43
      dbsyncer-listener/pom.xml
  78. 0 9
      dbsyncer-listener/src/main/java/org/dbsyncer/listener/Listener.java
  79. 0 27
      dbsyncer-listener/src/main/java/org/dbsyncer/listener/ListenerException.java
  80. 0 26
      dbsyncer-listener/src/main/java/org/dbsyncer/listener/ListenerFactory.java
  81. 0 19
      dbsyncer-listener/src/main/java/org/dbsyncer/listener/config/Host.java
  82. 0 22
      dbsyncer-listener/src/main/java/org/dbsyncer/listener/kafka/KafkaExtractor.java
  83. 43 45
      dbsyncer-manager/src/main/java/org/dbsyncer/manager/impl/IncrementPuller.java
  84. 0 7
      dbsyncer-parser/pom.xml
  85. 1 1
      dbsyncer-parser/src/main/java/org/dbsyncer/parser/ProfileComponent.java
  86. 3 3
      dbsyncer-parser/src/main/java/org/dbsyncer/parser/consumer/AbstractConsumer.java
  87. 3 3
      dbsyncer-parser/src/main/java/org/dbsyncer/parser/consumer/impl/LogConsumer.java
  88. 1 1
      dbsyncer-parser/src/main/java/org/dbsyncer/parser/consumer/impl/QuartzConsumer.java
  89. 1 1
      dbsyncer-parser/src/main/java/org/dbsyncer/parser/event/RefreshOffsetEvent.java
  90. 1 1
      dbsyncer-parser/src/main/java/org/dbsyncer/parser/flush/impl/BufferActuatorRouter.java
  91. 1 1
      dbsyncer-parser/src/main/java/org/dbsyncer/parser/impl/ProfileComponentImpl.java
  92. 1 1
      dbsyncer-parser/src/main/java/org/dbsyncer/parser/model/Mapping.java
  93. 3 3
      dbsyncer-parser/src/main/java/org/dbsyncer/parser/model/WriterRequest.java
  94. 1 1
      dbsyncer-parser/src/main/java/org/dbsyncer/parser/model/WriterResponse.java
  95. 3 0
      dbsyncer-sdk/pom.xml
  96. 1 1
      dbsyncer-sdk/src/main/java/org/dbsyncer/sdk/enums/ListenerTypeEnum.java
  97. 3 3
      dbsyncer-sdk/src/main/java/org/dbsyncer/sdk/listener/ChangedEvent.java
  98. 15 7
      dbsyncer-sdk/src/main/java/org/dbsyncer/sdk/listener/Listener.java
  99. 1 1
      dbsyncer-sdk/src/main/java/org/dbsyncer/sdk/listener/Watcher.java
  100. 3 3
      dbsyncer-sdk/src/main/java/org/dbsyncer/sdk/listener/event/CommonChangedEvent.java

+ 1 - 1
dbsyncer-biz/src/main/java/org/dbsyncer/biz/checker/impl/connector/FileConfigChecker.java

@@ -4,7 +4,7 @@ import org.dbsyncer.biz.checker.ConnectorConfigChecker;
 import org.dbsyncer.common.util.JsonUtil;
 import org.dbsyncer.common.util.StringUtil;
 import org.dbsyncer.connector.config.FileConfig;
-import org.dbsyncer.connector.model.FileSchema;
+import org.dbsyncer.connector.file.FileSchema;
 import org.springframework.stereotype.Component;
 import org.springframework.util.Assert;
 

+ 2 - 2
dbsyncer-biz/src/main/java/org/dbsyncer/biz/checker/impl/mapping/LogConfigChecker.java

@@ -1,8 +1,8 @@
 package org.dbsyncer.biz.checker.impl.mapping;
 
 import org.dbsyncer.biz.checker.MappingConfigChecker;
-import org.dbsyncer.listener.config.ListenerConfig;
-import org.dbsyncer.listener.enums.ListenerTypeEnum;
+import org.dbsyncer.connector.config.ListenerConfig;
+import org.dbsyncer.sdk.enums.ListenerTypeEnum;
 import org.dbsyncer.parser.model.Mapping;
 import org.springframework.stereotype.Component;
 import org.springframework.util.Assert;

+ 2 - 2
dbsyncer-biz/src/main/java/org/dbsyncer/biz/checker/impl/mapping/MappingChecker.java

@@ -8,8 +8,8 @@ import org.dbsyncer.common.util.CollectionUtils;
 import org.dbsyncer.common.util.JsonUtil;
 import org.dbsyncer.common.util.NumberUtil;
 import org.dbsyncer.common.util.StringUtil;
-import org.dbsyncer.listener.config.ListenerConfig;
-import org.dbsyncer.listener.enums.ListenerTypeEnum;
+import org.dbsyncer.connector.config.ListenerConfig;
+import org.dbsyncer.sdk.enums.ListenerTypeEnum;
 import org.dbsyncer.parser.ProfileComponent;
 import org.dbsyncer.sdk.enums.ModelEnum;
 import org.dbsyncer.parser.model.ConfigModel;

+ 2 - 2
dbsyncer-biz/src/main/java/org/dbsyncer/biz/checker/impl/mapping/TimingConfigChecker.java

@@ -2,8 +2,8 @@ package org.dbsyncer.biz.checker.impl.mapping;
 
 import org.dbsyncer.biz.checker.MappingConfigChecker;
 import org.dbsyncer.common.util.StringUtil;
-import org.dbsyncer.listener.config.ListenerConfig;
-import org.dbsyncer.listener.enums.ListenerTypeEnum;
+import org.dbsyncer.connector.config.ListenerConfig;
+import org.dbsyncer.sdk.enums.ListenerTypeEnum;
 import org.dbsyncer.parser.model.Mapping;
 import org.springframework.stereotype.Component;
 import org.springframework.util.Assert;

+ 1 - 1
dbsyncer-biz/src/main/java/org/dbsyncer/biz/impl/ConditionServiceImpl.java

@@ -3,7 +3,7 @@ package org.dbsyncer.biz.impl;
 import org.dbsyncer.biz.ConditionService;
 import org.dbsyncer.biz.vo.ConditionVo;
 import org.dbsyncer.connector.enums.FilterEnum;
-import org.dbsyncer.listener.enums.QuartzFilterEnum;
+import org.dbsyncer.connector.enums.QuartzFilterEnum;
 import org.dbsyncer.parser.ProfileComponent;
 import org.dbsyncer.sdk.enums.OperationEnum;
 import org.springframework.stereotype.Component;

+ 1 - 1
dbsyncer-biz/src/main/java/org/dbsyncer/biz/impl/DataSyncServiceImpl.java

@@ -10,7 +10,7 @@ import org.dbsyncer.common.util.DateFormatUtil;
 import org.dbsyncer.common.util.JsonUtil;
 import org.dbsyncer.common.util.NumberUtil;
 import org.dbsyncer.common.util.StringUtil;
-import org.dbsyncer.listener.event.RowChangedEvent;
+import org.dbsyncer.sdk.listener.event.RowChangedEvent;
 import org.dbsyncer.parser.ProfileComponent;
 import org.dbsyncer.parser.flush.impl.BufferActuatorRouter;
 import org.dbsyncer.parser.model.Meta;

+ 1 - 1
dbsyncer-biz/src/main/java/org/dbsyncer/biz/vo/ConditionVo.java

@@ -1,7 +1,7 @@
 package org.dbsyncer.biz.vo;
 
 import org.dbsyncer.connector.enums.FilterEnum;
-import org.dbsyncer.listener.enums.QuartzFilterEnum;
+import org.dbsyncer.connector.enums.QuartzFilterEnum;
 import org.dbsyncer.sdk.enums.OperationEnum;
 
 import java.util.List;

+ 10 - 0
dbsyncer-connector/pom.xml

@@ -75,6 +75,16 @@
             <artifactId>kafka-clients</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>com.github.jsqlparser</groupId>
+            <artifactId>jsqlparser</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.zendesk</groupId>
+            <artifactId>mysql-binlog-connector-java</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-log4j2</artifactId>

+ 5 - 4
dbsyncer-listener/src/main/java/org/dbsyncer/listener/AbstractDatabaseExtractor.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/AbstractDatabaseListener.java

@@ -1,11 +1,12 @@
-package org.dbsyncer.listener;
+package org.dbsyncer.connector;
 
-import org.dbsyncer.listener.event.RowChangedEvent;
 import org.dbsyncer.common.util.CollectionUtils;
 import org.dbsyncer.common.util.StringUtil;
-import org.dbsyncer.sdk.constant.ConnectorConstant;
 import org.dbsyncer.sdk.connector.database.AbstractDatabaseConnector;
 import org.dbsyncer.sdk.connector.database.DatabaseConnectorInstance;
+import org.dbsyncer.sdk.constant.ConnectorConstant;
+import org.dbsyncer.sdk.listener.ChangedEvent;
+import org.dbsyncer.sdk.listener.event.RowChangedEvent;
 import org.dbsyncer.sdk.model.Field;
 import org.dbsyncer.sdk.model.MetaInfo;
 import org.dbsyncer.sdk.model.Table;
@@ -23,7 +24,7 @@ import java.util.concurrent.ConcurrentHashMap;
  * @version 1.0.0
  * @date 2022/5/29 21:46
  */
-public abstract class AbstractDatabaseExtractor extends AbstractExtractor {
+public abstract class AbstractDatabaseListener extends AbstractListener {
 
     /**
      * 自定义SQL,支持1对多

+ 9 - 7
dbsyncer-listener/src/main/java/org/dbsyncer/listener/AbstractExtractor.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/AbstractListener.java

@@ -1,11 +1,13 @@
-package org.dbsyncer.listener;
+package org.dbsyncer.connector;
 
-import org.dbsyncer.connector.scheduled.ScheduledTaskService;
-import org.dbsyncer.listener.model.ChangedOffset;
 import org.dbsyncer.common.util.CollectionUtils;
-import org.dbsyncer.connector.ConnectorFactory;
+import org.dbsyncer.connector.config.ListenerConfig;
+import org.dbsyncer.connector.scheduled.ScheduledTaskService;
 import org.dbsyncer.sdk.constant.ConnectorConstant;
-import org.dbsyncer.listener.config.ListenerConfig;
+import org.dbsyncer.sdk.listener.ChangedEvent;
+import org.dbsyncer.sdk.listener.Listener;
+import org.dbsyncer.sdk.listener.Watcher;
+import org.dbsyncer.sdk.model.ChangedOffset;
 import org.dbsyncer.sdk.model.ConnectorConfig;
 import org.dbsyncer.sdk.model.Table;
 import org.slf4j.Logger;
@@ -23,7 +25,7 @@ import java.util.concurrent.TimeUnit;
  * @Author AE86
  * @Date 2020-05-25 22:35
  */
-public abstract class AbstractExtractor implements Extractor {
+public abstract class AbstractListener implements Listener {
 
     private final Logger logger = LoggerFactory.getLogger(getClass());
     protected ConnectorFactory connectorFactory;
@@ -137,7 +139,7 @@ public abstract class AbstractExtractor implements Extractor {
         this.filterTable = filterTable;
     }
 
-    public AbstractExtractor setSourceTable(List<Table> sourceTable) {
+    public AbstractListener setSourceTable(List<Table> sourceTable) {
         this.sourceTable = sourceTable;
         return this;
     }

+ 19 - 12
dbsyncer-connector/src/main/java/org/dbsyncer/connector/ConnectorFactory.java

@@ -8,6 +8,7 @@ import org.dbsyncer.sdk.config.ReaderConfig;
 import org.dbsyncer.sdk.config.WriterBatchConfig;
 import org.dbsyncer.sdk.connector.AbstractConnector;
 import org.dbsyncer.sdk.connector.ConnectorInstance;
+import org.dbsyncer.sdk.listener.Listener;
 import org.dbsyncer.sdk.model.ConnectorConfig;
 import org.dbsyncer.sdk.model.MetaInfo;
 import org.dbsyncer.sdk.model.Table;
@@ -89,19 +90,14 @@ public class ConnectorFactory implements DisposableBean {
     }
 
     /**
-     * 断开连接
+     * 获取监听器
      *
-     * @param config
+     * @param connectorType
+     * @param listenerType
      * @return
      */
-    public void disconnect(ConnectorConfig config) {
-        Assert.notNull(config, "ConnectorConfig can not be null.");
-        String cacheKey = getConnectorService(config).getConnectorInstanceCacheKey(config);
-        ConnectorInstance connectorInstance = pool.get(cacheKey);
-        if (connectorInstance != null) {
-            disconnect(connectorInstance);
-            pool.remove(cacheKey);
-        }
+    public Listener getListener(String connectorType, String listenerType) {
+        return getConnectorService(connectorType).getListener(listenerType);
     }
 
     /**
@@ -209,7 +205,7 @@ public class ConnectorFactory implements DisposableBean {
         return result;
     }
 
-    private ConnectorService getConnectorService(ConnectorConfig connectorConfig) {
+    public ConnectorService getConnectorService(ConnectorConfig connectorConfig) {
         Assert.notNull(connectorConfig, "ConnectorConfig can not null");
         return getConnectorService(connectorConfig.getConnectorType());
     }
@@ -229,8 +225,19 @@ public class ConnectorFactory implements DisposableBean {
     /**
      * 断开连接
      *
-     * @param connectorInstance
+     * @param config
+     * @return
      */
+    public void disconnect(ConnectorConfig config) {
+        Assert.notNull(config, "ConnectorConfig can not be null.");
+        String cacheKey = getConnectorService(config).getConnectorInstanceCacheKey(config);
+        ConnectorInstance connectorInstance = pool.get(cacheKey);
+        if (connectorInstance != null) {
+            disconnect(connectorInstance);
+            pool.remove(cacheKey);
+        }
+    }
+
     private void disconnect(ConnectorInstance connectorInstance) {
         Assert.notNull(connectorInstance, "ConnectorInstance can not be null.");
         getConnectorService(connectorInstance.getConfig()).disconnect(connectorInstance);

+ 2 - 2
dbsyncer-listener/src/main/java/org/dbsyncer/listener/config/ListenerConfig.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/config/ListenerConfig.java

@@ -1,6 +1,6 @@
-package org.dbsyncer.listener.config;
+package org.dbsyncer.connector.config;
 
-import org.dbsyncer.listener.enums.ListenerTypeEnum;
+import org.dbsyncer.sdk.enums.ListenerTypeEnum;
 
 /**
  * @author AE86

+ 6 - 6
dbsyncer-listener/src/main/java/org/dbsyncer/listener/enums/QuartzFilterEnum.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/enums/QuartzFilterEnum.java

@@ -1,11 +1,11 @@
-package org.dbsyncer.listener.enums;
+package org.dbsyncer.connector.enums;
 
 import org.dbsyncer.common.util.StringUtil;
-import org.dbsyncer.listener.quartz.QuartzFilter;
-import org.dbsyncer.listener.quartz.filter.DateFilter;
-import org.dbsyncer.listener.quartz.filter.TimestampFilter;
-import org.dbsyncer.listener.quartz.filter.YesDateFilter;
-import org.dbsyncer.listener.quartz.filter.YesTimestampFilter;
+import org.dbsyncer.connector.quartz.QuartzFilter;
+import org.dbsyncer.connector.quartz.filter.DateFilter;
+import org.dbsyncer.connector.quartz.filter.TimestampFilter;
+import org.dbsyncer.connector.quartz.filter.YesDateFilter;
+import org.dbsyncer.connector.quartz.filter.YesTimestampFilter;
 
 /**
  * @author AE86

+ 1 - 1
dbsyncer-listener/src/main/java/org/dbsyncer/listener/enums/TableOperationEnum.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/enums/TableOperationEnum.java

@@ -1,4 +1,4 @@
-package org.dbsyncer.listener.enums;
+package org.dbsyncer.connector.enums;
 
 public enum TableOperationEnum {
 

+ 10 - 0
dbsyncer-connector/src/main/java/org/dbsyncer/connector/es/ESConnector.java

@@ -15,7 +15,9 @@ import org.dbsyncer.sdk.config.WriterBatchConfig;
 import org.dbsyncer.sdk.connector.AbstractConnector;
 import org.dbsyncer.sdk.connector.ConnectorInstance;
 import org.dbsyncer.sdk.constant.ConnectorConstant;
+import org.dbsyncer.sdk.enums.ListenerTypeEnum;
 import org.dbsyncer.sdk.enums.OperationEnum;
+import org.dbsyncer.sdk.listener.Listener;
 import org.dbsyncer.sdk.model.Field;
 import org.dbsyncer.sdk.model.Filter;
 import org.dbsyncer.sdk.model.MetaInfo;
@@ -289,6 +291,14 @@ public final class ESConnector extends AbstractConnector implements ConnectorSer
         return Collections.EMPTY_MAP;
     }
 
+    @Override
+    public Listener getListener(String listenerType) {
+        if (ListenerTypeEnum.isTiming(listenerType)) {
+            return new ESQuartzListener();
+        }
+        return null;
+    }
+
     private void parseProperties(List<Field> fields, Map<String, Object> sourceMap) {
         Map<String, Object> properties = (Map<String, Object>) sourceMap.get(ESUtil.PROPERTIES);
         if (CollectionUtils.isEmpty(properties)) {

+ 8 - 5
dbsyncer-listener/src/main/java/org/dbsyncer/listener/quartz/ESQuartzExtractor.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/es/ESQuartzListener.java

@@ -1,10 +1,13 @@
-package org.dbsyncer.listener.quartz;
+package org.dbsyncer.connector.es;
 
 import org.dbsyncer.common.util.CollectionUtils;
 import org.dbsyncer.common.util.JsonUtil;
 import org.dbsyncer.common.util.StringUtil;
-import org.dbsyncer.listener.ListenerException;
-import org.dbsyncer.listener.enums.QuartzFilterEnum;
+import org.dbsyncer.connector.ConnectorException;
+import org.dbsyncer.connector.enums.QuartzFilterEnum;
+import org.dbsyncer.connector.quartz.AbstractQuartzListener;
+import org.dbsyncer.connector.quartz.Point;
+import org.dbsyncer.connector.quartz.QuartzFilter;
 import org.dbsyncer.sdk.constant.ConnectorConstant;
 import org.dbsyncer.sdk.model.Filter;
 
@@ -21,7 +24,7 @@ import java.util.Set;
  * @Author AE86
  * @Date 2021-09-01 20:35
  */
-public final class ESQuartzExtractor extends AbstractQuartzExtractor {
+public final class ESQuartzListener extends AbstractQuartzListener {
 
     @Override
     protected Point checkLastPoint(Map<String, String> command, int index) {
@@ -40,7 +43,7 @@ public final class ESQuartzExtractor extends AbstractQuartzExtractor {
         Set<String> set = new HashSet<>();
         for (Filter f : filters) {
             if (set.contains(f.getValue())) {
-                throw new ListenerException(String.format("系统参数%s存在多个.", f.getValue()));
+                throw new ConnectorException(String.format("系统参数%s存在多个.", f.getValue()));
             }
             QuartzFilterEnum filterEnum = QuartzFilterEnum.getQuartzFilterEnum(f.getValue());
             if (null != filterEnum) {

+ 1 - 1
dbsyncer-listener/src/main/java/org/dbsyncer/listener/file/BufferedRandomAccessFile.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/file/BufferedRandomAccessFile.java

@@ -1,4 +1,4 @@
-package org.dbsyncer.listener.file;
+package org.dbsyncer.connector.file;
 
 import java.io.File;
 import java.io.FileNotFoundException;

+ 10 - 1
dbsyncer-connector/src/main/java/org/dbsyncer/connector/file/FileConnector.java

@@ -7,12 +7,13 @@ import org.dbsyncer.common.util.CollectionUtils;
 import org.dbsyncer.common.util.StringUtil;
 import org.dbsyncer.connector.ConnectorException;
 import org.dbsyncer.connector.config.FileConfig;
-import org.dbsyncer.connector.model.FileSchema;
 import org.dbsyncer.sdk.config.CommandConfig;
 import org.dbsyncer.sdk.config.ReaderConfig;
 import org.dbsyncer.sdk.config.WriterBatchConfig;
 import org.dbsyncer.sdk.connector.AbstractConnector;
 import org.dbsyncer.sdk.connector.ConnectorInstance;
+import org.dbsyncer.sdk.enums.ListenerTypeEnum;
+import org.dbsyncer.sdk.listener.Listener;
 import org.dbsyncer.sdk.model.Field;
 import org.dbsyncer.sdk.model.MetaInfo;
 import org.dbsyncer.sdk.model.Table;
@@ -233,4 +234,12 @@ public final class FileConnector extends AbstractConnector implements ConnectorS
         return command;
     }
 
+    @Override
+    public Listener getListener(String listenerType) {
+        if (ListenerTypeEnum.isLog(listenerType)) {
+            return new FileListener();
+        }
+        return null;
+    }
+
 }

+ 0 - 1
dbsyncer-connector/src/main/java/org/dbsyncer/connector/file/FileConnectorInstance.java

@@ -2,7 +2,6 @@ package org.dbsyncer.connector.file;
 
 import org.dbsyncer.common.util.JsonUtil;
 import org.dbsyncer.connector.config.FileConfig;
-import org.dbsyncer.connector.model.FileSchema;
 import org.dbsyncer.sdk.connector.ConnectorInstance;
 import org.dbsyncer.sdk.model.Field;
 import org.springframework.util.Assert;

+ 7 - 10
dbsyncer-listener/src/main/java/org/dbsyncer/listener/file/FileExtractor.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/file/FileListener.java

@@ -1,18 +1,15 @@
-package org.dbsyncer.listener.file;
+package org.dbsyncer.connector.file;
 
 import org.apache.commons.io.IOUtils;
-import org.dbsyncer.listener.model.ChangedOffset;
-import org.dbsyncer.listener.event.RowChangedEvent;
+import org.dbsyncer.connector.ConnectorException;
+import org.dbsyncer.sdk.model.ChangedOffset;
+import org.dbsyncer.sdk.listener.event.RowChangedEvent;
 import org.dbsyncer.common.util.CollectionUtils;
 import org.dbsyncer.common.util.NumberUtil;
 import org.dbsyncer.common.util.StringUtil;
 import org.dbsyncer.connector.config.FileConfig;
 import org.dbsyncer.sdk.constant.ConnectorConstant;
-import org.dbsyncer.connector.file.FileConnectorInstance;
-import org.dbsyncer.connector.file.FileResolver;
-import org.dbsyncer.connector.model.FileSchema;
-import org.dbsyncer.listener.AbstractExtractor;
-import org.dbsyncer.listener.ListenerException;
+import org.dbsyncer.connector.AbstractListener;
 import org.dbsyncer.sdk.model.Field;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -43,7 +40,7 @@ import java.util.concurrent.locks.ReentrantLock;
  * @version 1.0.0
  * @date 2022/5/6 21:42
  */
-public class FileExtractor extends AbstractExtractor {
+public class FileListener extends AbstractListener {
 
     private final Logger logger = LoggerFactory.getLogger(getClass());
 
@@ -89,7 +86,7 @@ public class FileExtractor extends AbstractExtractor {
         } catch (Exception e) {
             logger.error("启动失败:{}", e.getMessage());
             closePipelineAndWatch();
-            throw new ListenerException(e);
+            throw new ConnectorException(e);
         } finally {
             connectLock.unlock();
         }

+ 1 - 1
dbsyncer-connector/src/main/java/org/dbsyncer/connector/model/FileSchema.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/file/FileSchema.java

@@ -1,4 +1,4 @@
-package org.dbsyncer.connector.model;
+package org.dbsyncer.connector.file;
 
 import org.dbsyncer.sdk.model.Field;
 

+ 6 - 0
dbsyncer-connector/src/main/java/org/dbsyncer/connector/kafka/KafkaConnector.java

@@ -10,6 +10,7 @@ import org.dbsyncer.sdk.connector.AbstractConnector;
 import org.dbsyncer.connector.ConnectorException;
 import org.dbsyncer.connector.config.KafkaConfig;
 import org.dbsyncer.sdk.connector.ConnectorInstance;
+import org.dbsyncer.sdk.listener.Listener;
 import org.dbsyncer.sdk.model.Field;
 import org.dbsyncer.sdk.model.MetaInfo;
 import org.dbsyncer.sdk.model.Table;
@@ -131,4 +132,9 @@ public class KafkaConnector extends AbstractConnector implements ConnectorServic
         return Collections.EMPTY_MAP;
     }
 
+    @Override
+    public Listener getListener(String listenerType) {
+        return null;
+    }
+
 }

+ 116 - 116
dbsyncer-listener/src/main/java/org/dbsyncer/listener/mysql/BinaryLogClient.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/mysql/BinaryLogClient.java

@@ -1,117 +1,117 @@
-package org.dbsyncer.listener.mysql;
-
-import com.github.shyiko.mysql.binlog.event.EventType;
-import com.github.shyiko.mysql.binlog.event.TableMapEventData;
-import com.github.shyiko.mysql.binlog.event.deserialization.EventDeserializer;
-import com.github.shyiko.mysql.binlog.network.AuthenticationException;
-import com.github.shyiko.mysql.binlog.network.ServerException;
-
-import java.util.Map;
-
-public interface BinaryLogClient {
-
-    /**
-     * Connect to the replication stream in a separate thread.
-     *
-     * @throws AuthenticationException if authentication fails
-     * @throws ServerException         if MySQL server responds with an error
-     * @throws Exception               if anything goes wrong while trying to connect
-     */
-    void connect() throws Exception;
-
-    /**
-     * Disconnect from the replication stream. Note that this does not reset binlogFilename/binlogPosition. Calling {@link #connect(int)}}
-     * again resumes client from where it left off.
-     */
-    void disconnect() throws Exception;
-
-    /**
-     * @return true if client is connected, false otherwise
-     */
-    boolean isConnected();
-
-    /**
-     * Register event listener. Note that multiple event listeners will be called in order they where registered.
-     */
-    void registerEventListener(BinaryLogRemoteClient.EventListener eventListener);
-
-    /**
-     * Register lifecycle listener. Note that multiple lifecycle listeners will be called in order they where registered.
-     */
-    void registerLifecycleListener(BinaryLogRemoteClient.LifecycleListener lifecycleListener);
-
-    /**
-     * @return binary log filename, nullable (and null be default). Note that this value is automatically tracked by the client and thus is
-     * subject to change (in response to {@link EventType#ROTATE}, for example).
-     * @see #setBinlogFilename(String)
-     */
-    String getBinlogFilename();
-
-    /**
-     * @param binlogFilename binary log filename. Special values are:
-     *                       <ul>
-     *                       <li>null, which turns on automatic resolution (resulting in the last known binlog and position). This is what
-     *                       happens by default when you don't specify binary log filename explicitly.</li>
-     *                       <li>"" (empty string), which instructs server to stream events starting from the oldest known binlog.</li>
-     *                       </ul>
-     * @see #getBinlogFilename()
-     */
-    void setBinlogFilename(String binlogFilename);
-
-    /**
-     * @return binary log position of the next event, 4 by default (which is a position of first event). Note that this value changes with
-     * each incoming event.
-     * @see #setBinlogPosition(long)
-     */
-    long getBinlogPosition();
-
-    /**
-     * @param binlogPosition binary log position. Any value less than 4 gets automatically adjusted to 4 on connect.
-     * @see #getBinlogPosition()
-     */
-    void setBinlogPosition(long binlogPosition);
-
-    /**
-     * @return event deserializer
-     * @see #setEventDeserializer(EventDeserializer)
-     */
-    EventDeserializer getEventDeserializer();
-
-    /**
-     * @param eventDeserializer custom event deserializer
-     */
-    void setEventDeserializer(EventDeserializer eventDeserializer);
-
-    /**
-     * @return tableMapEventByTableId
-     */
-    Map<Long, TableMapEventData> getTableMapEventByTableId();
-
-    /**
-     * @param tableMapEventByTableId tableMapEventMetadata
-     */
-    void setTableMapEventByTableId(Map<Long, TableMapEventData> tableMapEventByTableId);
-
-    /**
-     * 是否支持ddl
-     *
-     * @return
-     */
-    boolean isEnableDDL();
-
-    /**
-     * <p>true: ROTATE > FORMAT_DESCRIPTION > TABLE_MAP > WRITE_ROWS > UPDATE_ROWS > DELETE_ROWS > XID
-     * <p>false: Support all events
-     *
-     * @param enableDDL
-     */
-    void setEnableDDL(boolean enableDDL);
-
-    /**
-     * binlog-parser-127.0.0.1_3306_1
-     *
-     * @return workerThreadName
-     */
-    String getWorkerThreadName();
-
+package org.dbsyncer.connector.mysql;
+
+import com.github.shyiko.mysql.binlog.event.EventType;
+import com.github.shyiko.mysql.binlog.event.TableMapEventData;
+import com.github.shyiko.mysql.binlog.event.deserialization.EventDeserializer;
+import com.github.shyiko.mysql.binlog.network.AuthenticationException;
+import com.github.shyiko.mysql.binlog.network.ServerException;
+
+import java.util.Map;
+
+public interface BinaryLogClient {
+
+    /**
+     * Connect to the replication stream in a separate thread.
+     *
+     * @throws AuthenticationException if authentication fails
+     * @throws ServerException         if MySQL server responds with an error
+     * @throws Exception               if anything goes wrong while trying to connect
+     */
+    void connect() throws Exception;
+
+    /**
+     * Disconnect from the replication stream. Note that this does not reset binlogFilename/binlogPosition. Calling {@link #connect(int)}}
+     * again resumes client from where it left off.
+     */
+    void disconnect() throws Exception;
+
+    /**
+     * @return true if client is connected, false otherwise
+     */
+    boolean isConnected();
+
+    /**
+     * Register event listener. Note that multiple event listeners will be called in order they where registered.
+     */
+    void registerEventListener(BinaryLogRemoteClient.EventListener eventListener);
+
+    /**
+     * Register lifecycle listener. Note that multiple lifecycle listeners will be called in order they where registered.
+     */
+    void registerLifecycleListener(BinaryLogRemoteClient.LifecycleListener lifecycleListener);
+
+    /**
+     * @return binary log filename, nullable (and null be default). Note that this value is automatically tracked by the client and thus is
+     * subject to change (in response to {@link EventType#ROTATE}, for example).
+     * @see #setBinlogFilename(String)
+     */
+    String getBinlogFilename();
+
+    /**
+     * @param binlogFilename binary log filename. Special values are:
+     *                       <ul>
+     *                       <li>null, which turns on automatic resolution (resulting in the last known binlog and position). This is what
+     *                       happens by default when you don't specify binary log filename explicitly.</li>
+     *                       <li>"" (empty string), which instructs server to stream events starting from the oldest known binlog.</li>
+     *                       </ul>
+     * @see #getBinlogFilename()
+     */
+    void setBinlogFilename(String binlogFilename);
+
+    /**
+     * @return binary log position of the next event, 4 by default (which is a position of first event). Note that this value changes with
+     * each incoming event.
+     * @see #setBinlogPosition(long)
+     */
+    long getBinlogPosition();
+
+    /**
+     * @param binlogPosition binary log position. Any value less than 4 gets automatically adjusted to 4 on connect.
+     * @see #getBinlogPosition()
+     */
+    void setBinlogPosition(long binlogPosition);
+
+    /**
+     * @return event deserializer
+     * @see #setEventDeserializer(EventDeserializer)
+     */
+    EventDeserializer getEventDeserializer();
+
+    /**
+     * @param eventDeserializer custom event deserializer
+     */
+    void setEventDeserializer(EventDeserializer eventDeserializer);
+
+    /**
+     * @return tableMapEventByTableId
+     */
+    Map<Long, TableMapEventData> getTableMapEventByTableId();
+
+    /**
+     * @param tableMapEventByTableId tableMapEventMetadata
+     */
+    void setTableMapEventByTableId(Map<Long, TableMapEventData> tableMapEventByTableId);
+
+    /**
+     * 是否支持ddl
+     *
+     * @return
+     */
+    boolean isEnableDDL();
+
+    /**
+     * <p>true: ROTATE > FORMAT_DESCRIPTION > TABLE_MAP > WRITE_ROWS > UPDATE_ROWS > DELETE_ROWS > XID
+     * <p>false: Support all events
+     *
+     * @param enableDDL
+     */
+    void setEnableDDL(boolean enableDDL);
+
+    /**
+     * binlog-parser-127.0.0.1_3306_1
+     *
+     * @return workerThreadName
+     */
+    String getWorkerThreadName();
+
 }

+ 836 - 836
dbsyncer-listener/src/main/java/org/dbsyncer/listener/mysql/BinaryLogRemoteClient.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/mysql/BinaryLogRemoteClient.java

@@ -1,837 +1,837 @@
-package org.dbsyncer.listener.mysql;
-
-import com.github.shyiko.mysql.binlog.GtidSet;
-import com.github.shyiko.mysql.binlog.MariadbGtidSet;
-import com.github.shyiko.mysql.binlog.event.*;
-import com.github.shyiko.mysql.binlog.event.deserialization.*;
-import com.github.shyiko.mysql.binlog.io.ByteArrayInputStream;
-import com.github.shyiko.mysql.binlog.network.*;
-import com.github.shyiko.mysql.binlog.network.protocol.*;
-import com.github.shyiko.mysql.binlog.network.protocol.command.*;
-import org.dbsyncer.listener.ListenerException;
-import org.dbsyncer.listener.mysql.deserializer.DeleteDeserializer;
-import org.dbsyncer.listener.mysql.deserializer.UpdateDeserializer;
-import org.dbsyncer.listener.mysql.deserializer.WriteDeserializer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.X509TrustManager;
-import java.io.EOFException;
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.net.SocketException;
-import java.security.GeneralSecurityException;
-import java.security.cert.X509Certificate;
-import java.util.*;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-public class BinaryLogRemoteClient implements BinaryLogClient {
-
-    private final Logger logger = LoggerFactory.getLogger(getClass());
-
-    private static final SSLSocketFactory DEFAULT_REQUIRED_SSL_MODE_SOCKET_FACTORY = new DefaultSSLSocketFactory() {
-
-        @Override
-        protected void initSSLContext(SSLContext sc) throws GeneralSecurityException {
-            sc.init(null, new TrustManager[]{
-                    new X509TrustManager() {
-
-                        @Override
-                        public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {
-                        }
-
-                        @Override
-                        public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {
-                        }
-
-                        @Override
-                        public X509Certificate[] getAcceptedIssuers() {
-                            return new X509Certificate[0];
-                        }
-                    }
-            }, null);
-        }
-    };
-    private static final SSLSocketFactory DEFAULT_VERIFY_CA_SSL_MODE_SOCKET_FACTORY = new DefaultSSLSocketFactory();
-
-    // https://dev.mysql.com/doc/internals/en/sending-more-than-16mbyte.html
-    private static final int MAX_PACKET_LENGTH = 16777215;
-
-    private final String hostname;
-    private final int port;
-    private final String schema;
-    private final String username;
-    private final String password;
-    private SSLMode sslMode = SSLMode.DISABLED;
-
-    private EventDeserializer eventDeserializer;
-    private Map<Long, TableMapEventData> tableMapEventByTableId;
-    private boolean blocking = true;
-    private boolean enableDDL = false;
-    private long serverId = 65535;
-    private volatile String binlogFilename;
-    private volatile long binlogPosition = 4;
-    private volatile long connectionId;
-    private volatile PacketChannel channel;
-    private volatile boolean connected;
-    private Thread worker;
-    private Thread keepAlive;
-    private String workerThreadName;
-
-    private final Lock connectLock = new ReentrantLock();
-    private boolean gtidEnabled = false;
-    private final Object gtidSetAccessLock = new Object();
-    private GtidSet gtidSet;
-    private String gtid;
-    private boolean tx;
-    private boolean gtidSetFallbackToPurged;
-    private boolean useBinlogFilenamePositionInGtidMode;
-    private Boolean isMariaDB;
-
-    private final List<BinaryLogRemoteClient.EventListener> eventListeners = new CopyOnWriteArrayList<>();
-    private final List<BinaryLogRemoteClient.LifecycleListener> lifecycleListeners = new CopyOnWriteArrayList<>();
-
-    /**
-     * Alias for BinaryLogRemoteClient(hostname, port, &lt;no schema&gt; = null, username, password).
-     *
-     * @see BinaryLogRemoteClient#BinaryLogRemoteClient(String, int, String, String, String, long)
-     */
-    public BinaryLogRemoteClient(String hostname, int port, String username, String password) throws IOException {
-        this(hostname, port, null, username, password, 0L);
-    }
-
-    /**
-     * @param hostname mysql server hostname
-     * @param port     mysql server port
-     * @param schema   database name, nullable. Note that this parameter has nothing to do with event filtering. It's used only during the
-     *                 authentication.
-     * @param username login name
-     * @param password password
-     * @param serverId serverId
-     */
-    public BinaryLogRemoteClient(String hostname, int port, String schema, String username, String password, long serverId) throws IOException {
-        this.hostname = hostname;
-        this.port = port;
-        this.schema = schema;
-        this.username = username;
-        this.password = password;
-        this.serverId = randomPort(serverId);
-    }
-
-    @Override
-    public void connect() throws Exception {
-        try {
-            connectLock.lock();
-            if (connected) {
-                throw new IllegalStateException("BinaryLogRemoteClient is already connected");
-            }
-            setConfig();
-            openChannel();
-            connected = true;
-            // new keepalive thread
-            spawnKeepAliveThread();
-
-            // dump binary log
-            requestBinaryLogStream();
-            ensureEventDeserializerHasRequiredEDDs();
-
-            // new listen thread
-            spawnWorkerThread();
-            lifecycleListeners.forEach(listener -> listener.onConnect(this));
-        } finally {
-            connectLock.unlock();
-        }
-    }
-
-    @Override
-    public void disconnect() throws Exception {
-        if (connected) {
-            try {
-                connectLock.lock();
-                closeChannel(channel);
-                connected = false;
-                if (null != this.worker && !worker.isInterrupted()) {
-                    this.worker.interrupt();
-                    this.worker = null;
-                }
-                if (null != this.keepAlive && !keepAlive.isInterrupted()) {
-                    this.keepAlive.interrupt();
-                    this.keepAlive = null;
-                }
-                lifecycleListeners.forEach(listener -> listener.onDisconnect(this));
-            } finally {
-                connectLock.unlock();
-            }
-        }
-    }
-
-    @Override
-    public boolean isConnected() {
-        return this.connected;
-    }
-
-    @Override
-    public void registerEventListener(BinaryLogRemoteClient.EventListener eventListener) {
-        eventListeners.add(eventListener);
-    }
-
-    @Override
-    public void registerLifecycleListener(BinaryLogRemoteClient.LifecycleListener lifecycleListener) {
-        lifecycleListeners.add(lifecycleListener);
-    }
-
-    private String createClientId() {
-        return new StringBuilder(hostname).append(":").append(port).append("_").append(connectionId).toString();
-    }
-
-    private void openChannel() throws IOException {
-        try {
-            Socket socket = new Socket();
-            socket.connect(new InetSocketAddress(hostname, port), 3000);
-            channel = new PacketChannel(socket);
-            if (channel.getInputStream().peek() == -1) {
-                throw new EOFException();
-            }
-        } catch (IOException e) {
-            closeChannel(channel);
-            throw new IOException("Failed to connect to MySQL on " + hostname + ":" + port + ". Please make sure it's running.", e);
-        }
-        GreetingPacket greetingPacket = receiveGreeting(channel);
-        detectMariaDB(greetingPacket);
-        tryUpgradeToSSL(greetingPacket);
-        new Authenticator(greetingPacket, channel, schema, username, password).authenticate();
-        channel.authenticationComplete();
-
-        connectionId = greetingPacket.getThreadId();
-        if ("".equals(binlogFilename)) {
-            setupGtidSet();
-        }
-        if (binlogFilename == null) {
-            fetchBinlogFilenameAndPosition();
-        }
-        if (binlogPosition < 4) {
-            logger.warn("Binary log position adjusted from {} to {}", binlogPosition, 4);
-            binlogPosition = 4;
-        }
-        ChecksumType checksumType = fetchBinlogChecksum();
-        if (checksumType != ChecksumType.NONE) {
-            confirmSupportOfChecksum(channel, checksumType);
-        }
-        synchronized (gtidSetAccessLock) {
-            String position = gtidSet != null ? gtidSet.toString() : binlogFilename + "/" + binlogPosition;
-            logger.info("Connected to {}:{} at {} (sid:{}, cid:{})", hostname, port, position, serverId, connectionId);
-        }
-    }
-
-    private void detectMariaDB(GreetingPacket packet) {
-        String serverVersion = packet.getServerVersion();
-        if (serverVersion == null) {
-            return;
-        }
-        this.isMariaDB = serverVersion.toLowerCase().contains("mariadb");
-    }
-
-    private boolean tryUpgradeToSSL(GreetingPacket greetingPacket) throws IOException {
-        if (sslMode != SSLMode.DISABLED) {
-            boolean serverSupportsSSL = (greetingPacket.getServerCapabilities() & ClientCapabilities.SSL) != 0;
-            if (!serverSupportsSSL && (sslMode == SSLMode.REQUIRED || sslMode == SSLMode.VERIFY_CA ||
-                    sslMode == SSLMode.VERIFY_IDENTITY)) {
-                throw new IOException("MySQL server does not support SSL");
-            }
-            if (serverSupportsSSL) {
-                SSLRequestCommand sslRequestCommand = new SSLRequestCommand();
-                int collation = greetingPacket.getServerCollation();
-                sslRequestCommand.setCollation(collation);
-                channel.write(sslRequestCommand);
-                SSLSocketFactory sslSocketFactory = sslMode == SSLMode.REQUIRED || sslMode == SSLMode.PREFERRED ?
-                                        DEFAULT_REQUIRED_SSL_MODE_SOCKET_FACTORY :
-                                        DEFAULT_VERIFY_CA_SSL_MODE_SOCKET_FACTORY;
-                channel.upgradeToSSL(sslSocketFactory, sslMode == SSLMode.VERIFY_IDENTITY ? new TLSHostnameVerifier() : null);
-                logger.info("SSL enabled");
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private void listenForEventPackets(final PacketChannel channel) {
-        ByteArrayInputStream inputStream = channel.getInputStream();
-        try {
-            while (inputStream.peek() != -1) {
-                int packetLength = inputStream.readInteger(3);
-                //noinspection ResultOfMethodCallIgnored
-                // 1 byte for sequence
-                inputStream.skip(1);
-                int marker = inputStream.read();
-                // 255 error
-                if (marker == 0xFF) {
-                    ErrorPacket errorPacket = new ErrorPacket(inputStream.read(packetLength - 1));
-                    throw new ServerException(errorPacket.getErrorMessage(), errorPacket.getErrorCode(),
-                            errorPacket.getSqlState());
-                }
-                // 254 empty
-                if (marker == 0xFE && !blocking) {
-                    break;
-                }
-                Event event;
-                try {
-                    event = eventDeserializer.nextEvent(packetLength == MAX_PACKET_LENGTH ?
-                            new ByteArrayInputStream(readPacketSplitInChunks(inputStream, packetLength - 1)) :
-                            inputStream);
-                    if (event == null) {
-                        throw new EOFException();
-                    }
-                } catch (Exception e) {
-                    Throwable cause = e instanceof EventDataDeserializationException ? e.getCause() : e;
-                    if (cause instanceof EOFException || cause instanceof SocketException) {
-                        throw e;
-                    }
-                    lifecycleListeners.forEach(listener -> listener.onEventDeserializationFailure(this, e));
-                    continue;
-                }
-                if (connected) {
-                    updateGtidSet(event);
-                    notifyEventListeners(event);
-                    updateClientBinlogFilenameAndPosition(event);
-                }
-            }
-        } catch (Exception e) {
-            lifecycleListeners.forEach(listener -> listener.onCommunicationFailure(this, e));
-        }
-    }
-
-    private void ensureEventDeserializerHasRequiredEDDs() {
-        ensureEventDataDeserializerIfPresent(EventType.ROTATE, RotateEventDataDeserializer.class);
-        synchronized (gtidSetAccessLock) {
-            if (this.gtidEnabled) {
-                ensureEventDataDeserializerIfPresent(EventType.GTID, GtidEventDataDeserializer.class);
-                ensureEventDataDeserializerIfPresent(EventType.QUERY, QueryEventDataDeserializer.class);
-                ensureEventDataDeserializerIfPresent(EventType.ANNOTATE_ROWS, AnnotateRowsEventDataDeserializer.class);
-                ensureEventDataDeserializerIfPresent(EventType.MARIADB_GTID, MariadbGtidEventDataDeserializer.class);
-                ensureEventDataDeserializerIfPresent(EventType.MARIADB_GTID_LIST, MariadbGtidListEventDataDeserializer.class);
-            }
-        }
-    }
-
-    protected void checkError(byte[] packet) throws IOException {
-        if (packet[0] == (byte) 0xFF /* error */) {
-            byte[] bytes = Arrays.copyOfRange(packet, 1, packet.length);
-            ErrorPacket errorPacket = new ErrorPacket(bytes);
-            throw new ServerException(errorPacket.getErrorMessage(), errorPacket.getErrorCode(),
-                    errorPacket.getSqlState());
-        }
-    }
-
-    private GreetingPacket receiveGreeting(final PacketChannel channel) throws IOException {
-        byte[] initialHandshakePacket = channel.read();
-        /* error */
-        if (initialHandshakePacket[0] == (byte) 0xFF) {
-            byte[] bytes = Arrays.copyOfRange(initialHandshakePacket, 1, initialHandshakePacket.length);
-            ErrorPacket errorPacket = new ErrorPacket(bytes);
-            throw new ServerException(errorPacket.getErrorMessage(), errorPacket.getErrorCode(),
-                    errorPacket.getSqlState());
-        }
-        return new GreetingPacket(initialHandshakePacket);
-    }
-
-    private void requestBinaryLogStream() throws IOException {
-        long serverId = blocking ? this.serverId : 0; // http://bugs.mysql.com/bug.php?id=71178
-        if (this.isMariaDB) {
-            requestBinaryLogStreamMaria(serverId);
-            return;
-        }
-
-        requestBinaryLogStreamMysql(serverId);
-    }
-
-    private void requestBinaryLogStreamMysql(long serverId) throws IOException {
-        Command dumpBinaryLogCommand;
-        synchronized (gtidSetAccessLock) {
-            if (this.gtidEnabled) {
-                dumpBinaryLogCommand = new DumpBinaryLogGtidCommand(serverId,
-                        useBinlogFilenamePositionInGtidMode ? binlogFilename : "",
-                        useBinlogFilenamePositionInGtidMode ? binlogPosition : 4,
-                        gtidSet);
-            } else {
-                dumpBinaryLogCommand = new DumpBinaryLogCommand(serverId, binlogFilename, binlogPosition);
-            }
-        }
-        channel.write(dumpBinaryLogCommand);
-    }
-
-    private void requestBinaryLogStreamMaria(long serverId) throws IOException {
-        Command dumpBinaryLogCommand;
-        synchronized (gtidSetAccessLock) {
-            if (this.gtidEnabled) {
-                channel.write(new QueryCommand("SET @mariadb_slave_capability=4"));
-                checkError(channel.read());
-                logger.info(gtidSet.toString());
-                channel.write(new QueryCommand("SET @slave_connect_state = '" + gtidSet.toString() + "'"));
-                checkError(channel.read());
-                channel.write(new QueryCommand("SET @slave_gtid_strict_mode = 0"));
-                checkError(channel.read());
-                channel.write(new QueryCommand("SET @slave_gtid_ignore_duplicates = 0"));
-                checkError(channel.read());
-                dumpBinaryLogCommand = new DumpBinaryLogCommand(serverId, "", 0L, false);
-            } else {
-                dumpBinaryLogCommand = new DumpBinaryLogCommand(serverId, binlogFilename, binlogPosition);
-            }
-        }
-        channel.write(dumpBinaryLogCommand);
-    }
-
-    private void ensureEventDataDeserializerIfPresent(EventType eventType,
-                                                      Class<? extends EventDataDeserializer<?>> eventDataDeserializerClass) {
-        EventDataDeserializer<?> eventDataDeserializer = eventDeserializer.getEventDataDeserializer(eventType);
-        if (eventDataDeserializer.getClass() != eventDataDeserializerClass &&
-                eventDataDeserializer.getClass() != EventDeserializer.EventDataWrapper.Deserializer.class) {
-            EventDataDeserializer<?> internalEventDataDeserializer;
-            try {
-                internalEventDataDeserializer = eventDataDeserializerClass.newInstance();
-            } catch (Exception e) {
-                throw new RuntimeException(e);
-            }
-            eventDeserializer.setEventDataDeserializer(eventType,
-                    new EventDeserializer.EventDataWrapper.Deserializer(internalEventDataDeserializer,
-                            eventDataDeserializer));
-        }
-    }
-
-    private String fetchGtidPurged() throws IOException {
-        channel.write(new QueryCommand("show global variables like 'gtid_purged'"));
-        ResultSetRowPacket[] resultSet = readResultSet();
-        if (resultSet.length != 0) {
-            return resultSet[0].getValue(1).toUpperCase();
-        }
-        return "";
-    }
-
-    private void setupGtidSet() throws IOException {
-        if (!this.gtidEnabled)
-            return;
-
-        synchronized (gtidSetAccessLock) {
-            if (this.isMariaDB) {
-                if (gtidSet == null) {
-                    gtidSet = new MariadbGtidSet("");
-                } else if (!(gtidSet instanceof MariadbGtidSet)) {
-                    throw new RuntimeException("Connected to MariaDB but given a mysql GTID set!");
-                }
-            } else {
-                if (gtidSet == null && gtidSetFallbackToPurged) {
-                    gtidSet = new GtidSet(fetchGtidPurged());
-                } else if (gtidSet == null) {
-                    gtidSet = new GtidSet("");
-                } else if (gtidSet instanceof MariadbGtidSet) {
-                    throw new RuntimeException("Connected to Mysql but given a MariaDB GTID set!");
-                }
-            }
-        }
-
-    }
-
-    private void fetchBinlogFilenameAndPosition() throws IOException {
-        channel.write(new QueryCommand("show master status"));
-        ResultSetRowPacket[] resultSet = readResultSet();
-        if (resultSet.length == 0) {
-            throw new IOException("Failed to determine binlog filename/position");
-        }
-        ResultSetRowPacket resultSetRow = resultSet[0];
-        binlogFilename = resultSetRow.getValue(0);
-        binlogPosition = Long.parseLong(resultSetRow.getValue(1));
-    }
-
-    private ChecksumType fetchBinlogChecksum() throws IOException {
-        channel.write(new QueryCommand("show global variables like 'binlog_checksum'"));
-        ResultSetRowPacket[] resultSet = readResultSet();
-        if (resultSet.length == 0) {
-            return ChecksumType.NONE;
-        }
-        return ChecksumType.valueOf(resultSet[0].getValue(1).toUpperCase());
-    }
-
-    private void confirmSupportOfChecksum(final PacketChannel channel, ChecksumType checksumType) throws IOException {
-        channel.write(new QueryCommand("set @master_binlog_checksum= @@global.binlog_checksum"));
-        byte[] statementResult = channel.read();
-        checkError(statementResult);
-        eventDeserializer.setChecksumType(checksumType);
-    }
-
-    private byte[] readPacketSplitInChunks(ByteArrayInputStream inputStream, int packetLength) throws IOException {
-        byte[] result = inputStream.read(packetLength);
-        int chunkLength;
-        do {
-            chunkLength = inputStream.readInteger(3);
-            //noinspection ResultOfMethodCallIgnored
-            inputStream.skip(1); // 1 byte for sequence
-            result = Arrays.copyOf(result, result.length + chunkLength);
-            inputStream.fill(result, result.length - chunkLength, chunkLength);
-        } while (chunkLength == Packet.MAX_LENGTH);
-        return result;
-    }
-
-    private void updateClientBinlogFilenameAndPosition(Event event) {
-        EventHeader eventHeader = event.getHeader();
-        EventType eventType = eventHeader.getEventType();
-        if (eventType == EventType.ROTATE) {
-            RotateEventData rotateEventData = (RotateEventData) EventDeserializer.EventDataWrapper.internal(event.getData());
-            binlogFilename = rotateEventData.getBinlogFilename();
-            binlogPosition = rotateEventData.getBinlogPosition();
-        } else
-            // do not update binlogPosition on TABLE_MAP so that in case of reconnect (using a different instance of
-            // client) table mapping cache could be reconstructed before hitting row mutation event
-            if (eventType != EventType.TABLE_MAP && eventHeader instanceof EventHeaderV4) {
-                EventHeaderV4 trackableEventHeader = (EventHeaderV4) eventHeader;
-                long nextBinlogPosition = trackableEventHeader.getNextPosition();
-                if (nextBinlogPosition > 0) {
-                    binlogPosition = nextBinlogPosition;
-                }
-            }
-    }
-
-    private void updateGtidSet(Event event) {
-        synchronized (gtidSetAccessLock) {
-            if (gtidSet == null) {
-                return;
-            }
-        }
-        EventHeader eventHeader = event.getHeader();
-        switch (eventHeader.getEventType()) {
-            case GTID:
-                GtidEventData gtidEventData = (GtidEventData) EventDeserializer.EventDataWrapper.internal(event.getData());
-                gtid = gtidEventData.getGtid();
-                break;
-            case XID:
-                commitGtid();
-                tx = false;
-                break;
-            case QUERY:
-                QueryEventData queryEventData = (QueryEventData) EventDeserializer.EventDataWrapper.internal(event.getData());
-                String sql = queryEventData.getSql();
-                if (sql == null) {
-                    break;
-                }
-                if ("BEGIN".equals(sql)) {
-                    tx = true;
-                } else if ("COMMIT".equals(sql) || "ROLLBACK".equals(sql)) {
-                    commitGtid();
-                    tx = false;
-                } else if (!tx) {
-                    // auto-commit query, likely DDL
-                    commitGtid();
-                }
-            default:
-        }
-    }
-
-    private void commitGtid() {
-        if (gtid != null) {
-            synchronized (gtidSetAccessLock) {
-                gtidSet.add(gtid);
-            }
-        }
-    }
-
-    private ResultSetRowPacket[] readResultSet() throws IOException {
-        List<ResultSetRowPacket> resultSet = new LinkedList();
-        byte[] statementResult = channel.read();
-        if (statementResult[0] == (byte) 0xFF /* error */) {
-            byte[] bytes = Arrays.copyOfRange(statementResult, 1, statementResult.length);
-            ErrorPacket errorPacket = new ErrorPacket(bytes);
-            throw new ServerException(errorPacket.getErrorMessage(), errorPacket.getErrorCode(),
-                    errorPacket.getSqlState());
-        }
-        while ((channel.read())[0] != (byte) 0xFE /* eof */) { /* skip */ }
-        for (byte[] bytes; (bytes = channel.read())[0] != (byte) 0xFE /* eof */; ) {
-            resultSet.add(new ResultSetRowPacket(bytes));
-        }
-        return resultSet.toArray(new ResultSetRowPacket[0]);
-    }
-
-    private void closeChannel(final PacketChannel channel) throws IOException {
-        if (channel != null && channel.isOpen()) {
-            channel.close();
-        }
-    }
-
-    private void setConfig() {
-        if (null == tableMapEventByTableId) {
-            tableMapEventByTableId = new HashMap<>();
-        }
-
-        IdentityHashMap eventDataDeserializers = new IdentityHashMap();
-        if (null == eventDeserializer) {
-            this.eventDeserializer = new EventDeserializer(new EventHeaderV4Deserializer(), new NullEventDataDeserializer(), eventDataDeserializers, tableMapEventByTableId);
-        }
-
-        // Process event priority: RotateEvent > FormatDescriptionEvent > TableMapEvent > RowsEvent > XidEvent
-        eventDataDeserializers.put(EventType.ROTATE, new RotateEventDataDeserializer());
-        eventDataDeserializers.put(EventType.FORMAT_DESCRIPTION, new FormatDescriptionEventDataDeserializer());
-        eventDataDeserializers.put(EventType.TABLE_MAP, new TableMapEventDataDeserializer());
-        eventDataDeserializers.put(EventType.UPDATE_ROWS, new UpdateRowsEventDataDeserializer(tableMapEventByTableId));
-        eventDataDeserializers.put(EventType.WRITE_ROWS, new WriteRowsEventDataDeserializer(tableMapEventByTableId));
-        eventDataDeserializers.put(EventType.DELETE_ROWS, new DeleteRowsEventDataDeserializer(tableMapEventByTableId));
-        eventDataDeserializers.put(EventType.EXT_WRITE_ROWS, (new WriteDeserializer(tableMapEventByTableId)).setMayContainExtraInformation(true));
-        eventDataDeserializers.put(EventType.EXT_UPDATE_ROWS, (new UpdateDeserializer(tableMapEventByTableId)).setMayContainExtraInformation(true));
-        eventDataDeserializers.put(EventType.EXT_DELETE_ROWS, (new DeleteDeserializer(tableMapEventByTableId)).setMayContainExtraInformation(true));
-        eventDataDeserializers.put(EventType.XID, new XidEventDataDeserializer());
-
-        if (enableDDL) {
-            eventDataDeserializers.put(EventType.INTVAR, new IntVarEventDataDeserializer());
-            eventDataDeserializers.put(EventType.QUERY, new QueryEventDataDeserializer());
-            eventDataDeserializers.put(EventType.ROWS_QUERY, new RowsQueryEventDataDeserializer());
-            eventDataDeserializers.put(EventType.GTID, new GtidEventDataDeserializer());
-            eventDataDeserializers.put(EventType.PREVIOUS_GTIDS, new PreviousGtidSetDeserializer());
-            eventDataDeserializers.put(EventType.XA_PREPARE, new XAPrepareEventDataDeserializer());
-        }
-
-    }
-
-    private void notifyEventListeners(Event event) {
-        if (event.getData() instanceof EventDeserializer.EventDataWrapper) {
-            event = new Event(event.getHeader(), ((EventDeserializer.EventDataWrapper) event.getData()).getExternal());
-        }
-        for (BinaryLogRemoteClient.EventListener eventListener : eventListeners) {
-            try {
-                eventListener.onEvent(event);
-            } catch (Exception e) {
-                if (logger.isWarnEnabled()) {
-                    logger.warn(eventListener + " choked on " + event, e);
-                }
-            }
-        }
-    }
-
-    private long randomPort(long serverId) throws IOException {
-        if (0 == serverId) {
-            ServerSocket serverSocket = null;
-            try {
-                serverSocket = new ServerSocket(0);
-                return serverSocket.getLocalPort();
-            } finally {
-                if (null != serverSocket) {
-                    serverSocket.close();
-                }
-            }
-        }
-        return serverId;
-    }
-
-    private void spawnWorkerThread() {
-        this.worker = new Thread(() -> listenForEventPackets(channel));
-        this.worker.setDaemon(false);
-        this.workerThreadName = new StringBuilder("binlog-parser-").append(createClientId()).toString();
-        this.worker.setName(workerThreadName);
-        this.worker.start();
-    }
-
-    private void spawnKeepAliveThread() {
-        long keepAliveInterval = TimeUnit.SECONDS.toMillis(30);
-        String clientId = createClientId();
-        this.keepAlive = new Thread(() -> {
-            while (connected) {
-                try {
-                    Thread.sleep(keepAliveInterval);
-                } catch (InterruptedException e) {
-                    // expected in case of disconnect
-                }
-                boolean connectionLost = false;
-                try {
-                    channel.write(new PingCommand());
-                } catch (IOException e) {
-                    connectionLost = true;
-                }
-                if (connectionLost) {
-                    if (connected) {
-                        String error = String.format("keepalive: Trying to restore lost connection to %s", clientId);
-                        logger.info(error);
-                        try {
-                            lifecycleListeners.forEach(listener -> listener.onCommunicationFailure(this, new ListenerException(error)));
-                        } catch (Exception e) {
-                            logger.warn("keepalive error", e);
-                        }
-                    }
-                    break;
-                }
-            }
-        });
-        this.keepAlive.setDaemon(false);
-        this.keepAlive.setName(new StringBuilder("binlog-keepalive-").append(clientId).toString());
-        this.keepAlive.start();
-    }
-
-    @Override
-    public String getBinlogFilename() {
-        return binlogFilename;
-    }
-
-    @Override
-    public void setBinlogFilename(String binlogFilename) {
-        this.binlogFilename = binlogFilename;
-    }
-
-    @Override
-    public long getBinlogPosition() {
-        return binlogPosition;
-    }
-
-    @Override
-    public void setBinlogPosition(long binlogPosition) {
-        this.binlogPosition = binlogPosition;
-    }
-
-    @Override
-    public EventDeserializer getEventDeserializer() {
-        return eventDeserializer;
-    }
-
-    @Override
-    public void setEventDeserializer(EventDeserializer eventDeserializer) {
-        if (eventDeserializer == null) {
-            throw new IllegalArgumentException("Event deserializer cannot be NULL");
-        }
-        this.eventDeserializer = eventDeserializer;
-    }
-
-    @Override
-    public Map<Long, TableMapEventData> getTableMapEventByTableId() {
-        return tableMapEventByTableId;
-    }
-
-    @Override
-    public void setTableMapEventByTableId(Map<Long, TableMapEventData> tableMapEventByTableId) {
-        this.tableMapEventByTableId = tableMapEventByTableId;
-    }
-
-    public boolean isEnableDDL() {
-        return enableDDL;
-    }
-
-    public void setEnableDDL(boolean enableDDL) {
-        this.enableDDL = enableDDL;
-    }
-
-    @Override
-    public String getWorkerThreadName() {
-        return workerThreadName;
-    }
-
-    public SSLMode getSSLMode() {
-        return sslMode;
-    }
-
-    public void setSSLMode(SSLMode sslMode) {
-        if (sslMode == null) {
-            throw new IllegalArgumentException("SSL mode cannot be NULL");
-        }
-        this.sslMode = sslMode;
-    }
-
-    /**
-     * @return GTID set. Note that this value changes with each received GTID event (provided client is in GTID mode).
-     * @see #setGtidSet(String)
-     */
-    public String getGtidSet() {
-        synchronized (gtidSetAccessLock) {
-            return gtidSet != null ? gtidSet.toString() : null;
-        }
-    }
-
-    /**
-     * @param gtidStr GTID set string (can be an empty string).
-     * <p>NOTE #1: Any value but null will switch BinaryLogRemoteClient into a GTID mode (this will also set binlogFilename
-     * to "" (provided it's null) forcing MySQL to send events starting from the oldest known binlog (keep in mind
-     * that connection will fail if gtid_purged is anything but empty (unless
-     * {@link #setGtidSetFallbackToPurged(boolean)} is set to true))).
-     * <p>NOTE #2: GTID set is automatically updated with each incoming GTID event (provided GTID mode is on).
-     * @see #getGtidSet()
-     * @see #setGtidSetFallbackToPurged(boolean)
-     */
-    public void setGtidSet(String gtidStr) {
-        if (gtidStr == null)
-            return;
-
-        this.gtidEnabled = true;
-
-        if (this.binlogFilename == null) {
-            this.binlogFilename = "";
-        }
-        synchronized (gtidSetAccessLock) {
-            if (!gtidStr.equals("")) {
-                if (MariadbGtidSet.isMariaGtidSet(gtidStr)) {
-                    this.gtidSet = new MariadbGtidSet(gtidStr);
-                } else {
-                    this.gtidSet = new GtidSet(gtidStr);
-                }
-            }
-        }
-    }
-
-    /**
-     * @see #setGtidSetFallbackToPurged(boolean)
-     */
-    public boolean isGtidSetFallbackToPurged() {
-        return gtidSetFallbackToPurged;
-    }
-
-    /**
-     * @param gtidSetFallbackToPurged true if gtid_purged should be used as a fallback when gtidSet is set to "" and MySQL server has purged
-     *                                some of the binary logs, false otherwise (default).
-     */
-    public void setGtidSetFallbackToPurged(boolean gtidSetFallbackToPurged) {
-        this.gtidSetFallbackToPurged = gtidSetFallbackToPurged;
-    }
-
-    /**
-     * @see #setUseBinlogFilenamePositionInGtidMode(boolean)
-     */
-    public boolean isUseBinlogFilenamePositionInGtidMode() {
-        return useBinlogFilenamePositionInGtidMode;
-    }
-
-    /**
-     * @param useBinlogFilenamePositionInGtidMode true if MySQL server should start streaming events from a given {@link
-     *                                            #getBinlogFilename()} and {@link #getBinlogPosition()} instead of "the oldest known
-     *                                            binlog" when {@link #getGtidSet()} is set, false otherwise (default).
-     */
-    public void setUseBinlogFilenamePositionInGtidMode(boolean useBinlogFilenamePositionInGtidMode) {
-        this.useBinlogFilenamePositionInGtidMode = useBinlogFilenamePositionInGtidMode;
-    }
-
-    public interface EventListener {
-
-        void onEvent(Event event);
-    }
-
-    public interface LifecycleListener {
-
-        /**
-         * Called once client has successfully logged in but before started to receive binlog events.
-         */
-        void onConnect(BinaryLogRemoteClient client);
-
-        /**
-         * It's guarantied to be called before {@link #onDisconnect(BinaryLogRemoteClient)}) in case of communication failure.
-         */
-        void onCommunicationFailure(BinaryLogRemoteClient client, Exception ex);
-
-        /**
-         * Called in case of failed event deserialization. Note this type of error does NOT cause client to disconnect. If you wish to stop
-         * receiving events you'll need to fire client.disconnect() manually.
-         */
-        void onEventDeserializationFailure(BinaryLogRemoteClient client, Exception ex);
-
-        /**
-         * Called upon disconnect (regardless of the reason).
-         */
-        void onDisconnect(BinaryLogRemoteClient client);
-    }
-
+package org.dbsyncer.connector.mysql;
+
+import com.github.shyiko.mysql.binlog.GtidSet;
+import com.github.shyiko.mysql.binlog.MariadbGtidSet;
+import com.github.shyiko.mysql.binlog.event.*;
+import com.github.shyiko.mysql.binlog.event.deserialization.*;
+import com.github.shyiko.mysql.binlog.io.ByteArrayInputStream;
+import com.github.shyiko.mysql.binlog.network.*;
+import com.github.shyiko.mysql.binlog.network.protocol.*;
+import com.github.shyiko.mysql.binlog.network.protocol.command.*;
+import org.dbsyncer.connector.ConnectorException;
+import org.dbsyncer.connector.mysql.deserializer.DeleteDeserializer;
+import org.dbsyncer.connector.mysql.deserializer.UpdateDeserializer;
+import org.dbsyncer.connector.mysql.deserializer.WriteDeserializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import java.io.EOFException;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.security.GeneralSecurityException;
+import java.security.cert.X509Certificate;
+import java.util.*;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class BinaryLogRemoteClient implements BinaryLogClient {
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    private static final SSLSocketFactory DEFAULT_REQUIRED_SSL_MODE_SOCKET_FACTORY = new DefaultSSLSocketFactory() {
+
+        @Override
+        protected void initSSLContext(SSLContext sc) throws GeneralSecurityException {
+            sc.init(null, new TrustManager[]{
+                    new X509TrustManager() {
+
+                        @Override
+                        public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {
+                        }
+
+                        @Override
+                        public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {
+                        }
+
+                        @Override
+                        public X509Certificate[] getAcceptedIssuers() {
+                            return new X509Certificate[0];
+                        }
+                    }
+            }, null);
+        }
+    };
+    private static final SSLSocketFactory DEFAULT_VERIFY_CA_SSL_MODE_SOCKET_FACTORY = new DefaultSSLSocketFactory();
+
+    // https://dev.mysql.com/doc/internals/en/sending-more-than-16mbyte.html
+    private static final int MAX_PACKET_LENGTH = 16777215;
+
+    private final String hostname;
+    private final int port;
+    private final String schema;
+    private final String username;
+    private final String password;
+    private SSLMode sslMode = SSLMode.DISABLED;
+
+    private EventDeserializer eventDeserializer;
+    private Map<Long, TableMapEventData> tableMapEventByTableId;
+    private boolean blocking = true;
+    private boolean enableDDL = false;
+    private long serverId = 65535;
+    private volatile String binlogFilename;
+    private volatile long binlogPosition = 4;
+    private volatile long connectionId;
+    private volatile PacketChannel channel;
+    private volatile boolean connected;
+    private Thread worker;
+    private Thread keepAlive;
+    private String workerThreadName;
+
+    private final Lock connectLock = new ReentrantLock();
+    private boolean gtidEnabled = false;
+    private final Object gtidSetAccessLock = new Object();
+    private GtidSet gtidSet;
+    private String gtid;
+    private boolean tx;
+    private boolean gtidSetFallbackToPurged;
+    private boolean useBinlogFilenamePositionInGtidMode;
+    private Boolean isMariaDB;
+
+    private final List<BinaryLogRemoteClient.EventListener> eventListeners = new CopyOnWriteArrayList<>();
+    private final List<BinaryLogRemoteClient.LifecycleListener> lifecycleListeners = new CopyOnWriteArrayList<>();
+
+    /**
+     * Alias for BinaryLogRemoteClient(hostname, port, &lt;no schema&gt; = null, username, password).
+     *
+     * @see BinaryLogRemoteClient#BinaryLogRemoteClient(String, int, String, String, String, long)
+     */
+    public BinaryLogRemoteClient(String hostname, int port, String username, String password) throws IOException {
+        this(hostname, port, null, username, password, 0L);
+    }
+
+    /**
+     * @param hostname mysql server hostname
+     * @param port     mysql server port
+     * @param schema   database name, nullable. Note that this parameter has nothing to do with event filtering. It's used only during the
+     *                 authentication.
+     * @param username login name
+     * @param password password
+     * @param serverId serverId
+     */
+    public BinaryLogRemoteClient(String hostname, int port, String schema, String username, String password, long serverId) throws IOException {
+        this.hostname = hostname;
+        this.port = port;
+        this.schema = schema;
+        this.username = username;
+        this.password = password;
+        this.serverId = randomPort(serverId);
+    }
+
+    @Override
+    public void connect() throws Exception {
+        try {
+            connectLock.lock();
+            if (connected) {
+                throw new IllegalStateException("BinaryLogRemoteClient is already connected");
+            }
+            setConfig();
+            openChannel();
+            connected = true;
+            // new keepalive thread
+            spawnKeepAliveThread();
+
+            // dump binary log
+            requestBinaryLogStream();
+            ensureEventDeserializerHasRequiredEDDs();
+
+            // new listen thread
+            spawnWorkerThread();
+            lifecycleListeners.forEach(listener -> listener.onConnect(this));
+        } finally {
+            connectLock.unlock();
+        }
+    }
+
+    @Override
+    public void disconnect() throws Exception {
+        if (connected) {
+            try {
+                connectLock.lock();
+                closeChannel(channel);
+                connected = false;
+                if (null != this.worker && !worker.isInterrupted()) {
+                    this.worker.interrupt();
+                    this.worker = null;
+                }
+                if (null != this.keepAlive && !keepAlive.isInterrupted()) {
+                    this.keepAlive.interrupt();
+                    this.keepAlive = null;
+                }
+                lifecycleListeners.forEach(listener -> listener.onDisconnect(this));
+            } finally {
+                connectLock.unlock();
+            }
+        }
+    }
+
+    @Override
+    public boolean isConnected() {
+        return this.connected;
+    }
+
+    @Override
+    public void registerEventListener(BinaryLogRemoteClient.EventListener eventListener) {
+        eventListeners.add(eventListener);
+    }
+
+    @Override
+    public void registerLifecycleListener(BinaryLogRemoteClient.LifecycleListener lifecycleListener) {
+        lifecycleListeners.add(lifecycleListener);
+    }
+
+    private String createClientId() {
+        return new StringBuilder(hostname).append(":").append(port).append("_").append(connectionId).toString();
+    }
+
+    private void openChannel() throws IOException {
+        try {
+            Socket socket = new Socket();
+            socket.connect(new InetSocketAddress(hostname, port), 3000);
+            channel = new PacketChannel(socket);
+            if (channel.getInputStream().peek() == -1) {
+                throw new EOFException();
+            }
+        } catch (IOException e) {
+            closeChannel(channel);
+            throw new IOException("Failed to connect to MySQL on " + hostname + ":" + port + ". Please make sure it's running.", e);
+        }
+        GreetingPacket greetingPacket = receiveGreeting(channel);
+        detectMariaDB(greetingPacket);
+        tryUpgradeToSSL(greetingPacket);
+        new Authenticator(greetingPacket, channel, schema, username, password).authenticate();
+        channel.authenticationComplete();
+
+        connectionId = greetingPacket.getThreadId();
+        if ("".equals(binlogFilename)) {
+            setupGtidSet();
+        }
+        if (binlogFilename == null) {
+            fetchBinlogFilenameAndPosition();
+        }
+        if (binlogPosition < 4) {
+            logger.warn("Binary log position adjusted from {} to {}", binlogPosition, 4);
+            binlogPosition = 4;
+        }
+        ChecksumType checksumType = fetchBinlogChecksum();
+        if (checksumType != ChecksumType.NONE) {
+            confirmSupportOfChecksum(channel, checksumType);
+        }
+        synchronized (gtidSetAccessLock) {
+            String position = gtidSet != null ? gtidSet.toString() : binlogFilename + "/" + binlogPosition;
+            logger.info("Connected to {}:{} at {} (sid:{}, cid:{})", hostname, port, position, serverId, connectionId);
+        }
+    }
+
+    private void detectMariaDB(GreetingPacket packet) {
+        String serverVersion = packet.getServerVersion();
+        if (serverVersion == null) {
+            return;
+        }
+        this.isMariaDB = serverVersion.toLowerCase().contains("mariadb");
+    }
+
+    private boolean tryUpgradeToSSL(GreetingPacket greetingPacket) throws IOException {
+        if (sslMode != SSLMode.DISABLED) {
+            boolean serverSupportsSSL = (greetingPacket.getServerCapabilities() & ClientCapabilities.SSL) != 0;
+            if (!serverSupportsSSL && (sslMode == SSLMode.REQUIRED || sslMode == SSLMode.VERIFY_CA ||
+                    sslMode == SSLMode.VERIFY_IDENTITY)) {
+                throw new IOException("MySQL server does not support SSL");
+            }
+            if (serverSupportsSSL) {
+                SSLRequestCommand sslRequestCommand = new SSLRequestCommand();
+                int collation = greetingPacket.getServerCollation();
+                sslRequestCommand.setCollation(collation);
+                channel.write(sslRequestCommand);
+                SSLSocketFactory sslSocketFactory = sslMode == SSLMode.REQUIRED || sslMode == SSLMode.PREFERRED ?
+                                        DEFAULT_REQUIRED_SSL_MODE_SOCKET_FACTORY :
+                                        DEFAULT_VERIFY_CA_SSL_MODE_SOCKET_FACTORY;
+                channel.upgradeToSSL(sslSocketFactory, sslMode == SSLMode.VERIFY_IDENTITY ? new TLSHostnameVerifier() : null);
+                logger.info("SSL enabled");
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void listenForEventPackets(final PacketChannel channel) {
+        ByteArrayInputStream inputStream = channel.getInputStream();
+        try {
+            while (inputStream.peek() != -1) {
+                int packetLength = inputStream.readInteger(3);
+                //noinspection ResultOfMethodCallIgnored
+                // 1 byte for sequence
+                inputStream.skip(1);
+                int marker = inputStream.read();
+                // 255 error
+                if (marker == 0xFF) {
+                    ErrorPacket errorPacket = new ErrorPacket(inputStream.read(packetLength - 1));
+                    throw new ServerException(errorPacket.getErrorMessage(), errorPacket.getErrorCode(),
+                            errorPacket.getSqlState());
+                }
+                // 254 empty
+                if (marker == 0xFE && !blocking) {
+                    break;
+                }
+                Event event;
+                try {
+                    event = eventDeserializer.nextEvent(packetLength == MAX_PACKET_LENGTH ?
+                            new ByteArrayInputStream(readPacketSplitInChunks(inputStream, packetLength - 1)) :
+                            inputStream);
+                    if (event == null) {
+                        throw new EOFException();
+                    }
+                } catch (Exception e) {
+                    Throwable cause = e instanceof EventDataDeserializationException ? e.getCause() : e;
+                    if (cause instanceof EOFException || cause instanceof SocketException) {
+                        throw e;
+                    }
+                    lifecycleListeners.forEach(listener -> listener.onEventDeserializationFailure(this, e));
+                    continue;
+                }
+                if (connected) {
+                    updateGtidSet(event);
+                    notifyEventListeners(event);
+                    updateClientBinlogFilenameAndPosition(event);
+                }
+            }
+        } catch (Exception e) {
+            lifecycleListeners.forEach(listener -> listener.onCommunicationFailure(this, e));
+        }
+    }
+
+    private void ensureEventDeserializerHasRequiredEDDs() {
+        ensureEventDataDeserializerIfPresent(EventType.ROTATE, RotateEventDataDeserializer.class);
+        synchronized (gtidSetAccessLock) {
+            if (this.gtidEnabled) {
+                ensureEventDataDeserializerIfPresent(EventType.GTID, GtidEventDataDeserializer.class);
+                ensureEventDataDeserializerIfPresent(EventType.QUERY, QueryEventDataDeserializer.class);
+                ensureEventDataDeserializerIfPresent(EventType.ANNOTATE_ROWS, AnnotateRowsEventDataDeserializer.class);
+                ensureEventDataDeserializerIfPresent(EventType.MARIADB_GTID, MariadbGtidEventDataDeserializer.class);
+                ensureEventDataDeserializerIfPresent(EventType.MARIADB_GTID_LIST, MariadbGtidListEventDataDeserializer.class);
+            }
+        }
+    }
+
+    protected void checkError(byte[] packet) throws IOException {
+        if (packet[0] == (byte) 0xFF /* error */) {
+            byte[] bytes = Arrays.copyOfRange(packet, 1, packet.length);
+            ErrorPacket errorPacket = new ErrorPacket(bytes);
+            throw new ServerException(errorPacket.getErrorMessage(), errorPacket.getErrorCode(),
+                    errorPacket.getSqlState());
+        }
+    }
+
+    private GreetingPacket receiveGreeting(final PacketChannel channel) throws IOException {
+        byte[] initialHandshakePacket = channel.read();
+        /* error */
+        if (initialHandshakePacket[0] == (byte) 0xFF) {
+            byte[] bytes = Arrays.copyOfRange(initialHandshakePacket, 1, initialHandshakePacket.length);
+            ErrorPacket errorPacket = new ErrorPacket(bytes);
+            throw new ServerException(errorPacket.getErrorMessage(), errorPacket.getErrorCode(),
+                    errorPacket.getSqlState());
+        }
+        return new GreetingPacket(initialHandshakePacket);
+    }
+
+    private void requestBinaryLogStream() throws IOException {
+        long serverId = blocking ? this.serverId : 0; // http://bugs.mysql.com/bug.php?id=71178
+        if (this.isMariaDB) {
+            requestBinaryLogStreamMaria(serverId);
+            return;
+        }
+
+        requestBinaryLogStreamMysql(serverId);
+    }
+
+    private void requestBinaryLogStreamMysql(long serverId) throws IOException {
+        Command dumpBinaryLogCommand;
+        synchronized (gtidSetAccessLock) {
+            if (this.gtidEnabled) {
+                dumpBinaryLogCommand = new DumpBinaryLogGtidCommand(serverId,
+                        useBinlogFilenamePositionInGtidMode ? binlogFilename : "",
+                        useBinlogFilenamePositionInGtidMode ? binlogPosition : 4,
+                        gtidSet);
+            } else {
+                dumpBinaryLogCommand = new DumpBinaryLogCommand(serverId, binlogFilename, binlogPosition);
+            }
+        }
+        channel.write(dumpBinaryLogCommand);
+    }
+
+    private void requestBinaryLogStreamMaria(long serverId) throws IOException {
+        Command dumpBinaryLogCommand;
+        synchronized (gtidSetAccessLock) {
+            if (this.gtidEnabled) {
+                channel.write(new QueryCommand("SET @mariadb_slave_capability=4"));
+                checkError(channel.read());
+                logger.info(gtidSet.toString());
+                channel.write(new QueryCommand("SET @slave_connect_state = '" + gtidSet.toString() + "'"));
+                checkError(channel.read());
+                channel.write(new QueryCommand("SET @slave_gtid_strict_mode = 0"));
+                checkError(channel.read());
+                channel.write(new QueryCommand("SET @slave_gtid_ignore_duplicates = 0"));
+                checkError(channel.read());
+                dumpBinaryLogCommand = new DumpBinaryLogCommand(serverId, "", 0L, false);
+            } else {
+                dumpBinaryLogCommand = new DumpBinaryLogCommand(serverId, binlogFilename, binlogPosition);
+            }
+        }
+        channel.write(dumpBinaryLogCommand);
+    }
+
+    private void ensureEventDataDeserializerIfPresent(EventType eventType,
+                                                      Class<? extends EventDataDeserializer<?>> eventDataDeserializerClass) {
+        EventDataDeserializer<?> eventDataDeserializer = eventDeserializer.getEventDataDeserializer(eventType);
+        if (eventDataDeserializer.getClass() != eventDataDeserializerClass &&
+                eventDataDeserializer.getClass() != EventDeserializer.EventDataWrapper.Deserializer.class) {
+            EventDataDeserializer<?> internalEventDataDeserializer;
+            try {
+                internalEventDataDeserializer = eventDataDeserializerClass.newInstance();
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+            eventDeserializer.setEventDataDeserializer(eventType,
+                    new EventDeserializer.EventDataWrapper.Deserializer(internalEventDataDeserializer,
+                            eventDataDeserializer));
+        }
+    }
+
+    private String fetchGtidPurged() throws IOException {
+        channel.write(new QueryCommand("show global variables like 'gtid_purged'"));
+        ResultSetRowPacket[] resultSet = readResultSet();
+        if (resultSet.length != 0) {
+            return resultSet[0].getValue(1).toUpperCase();
+        }
+        return "";
+    }
+
+    private void setupGtidSet() throws IOException {
+        if (!this.gtidEnabled)
+            return;
+
+        synchronized (gtidSetAccessLock) {
+            if (this.isMariaDB) {
+                if (gtidSet == null) {
+                    gtidSet = new MariadbGtidSet("");
+                } else if (!(gtidSet instanceof MariadbGtidSet)) {
+                    throw new RuntimeException("Connected to MariaDB but given a mysql GTID set!");
+                }
+            } else {
+                if (gtidSet == null && gtidSetFallbackToPurged) {
+                    gtidSet = new GtidSet(fetchGtidPurged());
+                } else if (gtidSet == null) {
+                    gtidSet = new GtidSet("");
+                } else if (gtidSet instanceof MariadbGtidSet) {
+                    throw new RuntimeException("Connected to Mysql but given a MariaDB GTID set!");
+                }
+            }
+        }
+
+    }
+
+    private void fetchBinlogFilenameAndPosition() throws IOException {
+        channel.write(new QueryCommand("show master status"));
+        ResultSetRowPacket[] resultSet = readResultSet();
+        if (resultSet.length == 0) {
+            throw new IOException("Failed to determine binlog filename/position");
+        }
+        ResultSetRowPacket resultSetRow = resultSet[0];
+        binlogFilename = resultSetRow.getValue(0);
+        binlogPosition = Long.parseLong(resultSetRow.getValue(1));
+    }
+
+    private ChecksumType fetchBinlogChecksum() throws IOException {
+        channel.write(new QueryCommand("show global variables like 'binlog_checksum'"));
+        ResultSetRowPacket[] resultSet = readResultSet();
+        if (resultSet.length == 0) {
+            return ChecksumType.NONE;
+        }
+        return ChecksumType.valueOf(resultSet[0].getValue(1).toUpperCase());
+    }
+
+    private void confirmSupportOfChecksum(final PacketChannel channel, ChecksumType checksumType) throws IOException {
+        channel.write(new QueryCommand("set @master_binlog_checksum= @@global.binlog_checksum"));
+        byte[] statementResult = channel.read();
+        checkError(statementResult);
+        eventDeserializer.setChecksumType(checksumType);
+    }
+
+    private byte[] readPacketSplitInChunks(ByteArrayInputStream inputStream, int packetLength) throws IOException {
+        byte[] result = inputStream.read(packetLength);
+        int chunkLength;
+        do {
+            chunkLength = inputStream.readInteger(3);
+            //noinspection ResultOfMethodCallIgnored
+            inputStream.skip(1); // 1 byte for sequence
+            result = Arrays.copyOf(result, result.length + chunkLength);
+            inputStream.fill(result, result.length - chunkLength, chunkLength);
+        } while (chunkLength == Packet.MAX_LENGTH);
+        return result;
+    }
+
+    private void updateClientBinlogFilenameAndPosition(Event event) {
+        EventHeader eventHeader = event.getHeader();
+        EventType eventType = eventHeader.getEventType();
+        if (eventType == EventType.ROTATE) {
+            RotateEventData rotateEventData = (RotateEventData) EventDeserializer.EventDataWrapper.internal(event.getData());
+            binlogFilename = rotateEventData.getBinlogFilename();
+            binlogPosition = rotateEventData.getBinlogPosition();
+        } else
+            // do not update binlogPosition on TABLE_MAP so that in case of reconnect (using a different instance of
+            // client) table mapping cache could be reconstructed before hitting row mutation event
+            if (eventType != EventType.TABLE_MAP && eventHeader instanceof EventHeaderV4) {
+                EventHeaderV4 trackableEventHeader = (EventHeaderV4) eventHeader;
+                long nextBinlogPosition = trackableEventHeader.getNextPosition();
+                if (nextBinlogPosition > 0) {
+                    binlogPosition = nextBinlogPosition;
+                }
+            }
+    }
+
+    private void updateGtidSet(Event event) {
+        synchronized (gtidSetAccessLock) {
+            if (gtidSet == null) {
+                return;
+            }
+        }
+        EventHeader eventHeader = event.getHeader();
+        switch (eventHeader.getEventType()) {
+            case GTID:
+                GtidEventData gtidEventData = (GtidEventData) EventDeserializer.EventDataWrapper.internal(event.getData());
+                gtid = gtidEventData.getGtid();
+                break;
+            case XID:
+                commitGtid();
+                tx = false;
+                break;
+            case QUERY:
+                QueryEventData queryEventData = (QueryEventData) EventDeserializer.EventDataWrapper.internal(event.getData());
+                String sql = queryEventData.getSql();
+                if (sql == null) {
+                    break;
+                }
+                if ("BEGIN".equals(sql)) {
+                    tx = true;
+                } else if ("COMMIT".equals(sql) || "ROLLBACK".equals(sql)) {
+                    commitGtid();
+                    tx = false;
+                } else if (!tx) {
+                    // auto-commit query, likely DDL
+                    commitGtid();
+                }
+            default:
+        }
+    }
+
+    private void commitGtid() {
+        if (gtid != null) {
+            synchronized (gtidSetAccessLock) {
+                gtidSet.add(gtid);
+            }
+        }
+    }
+
+    private ResultSetRowPacket[] readResultSet() throws IOException {
+        List<ResultSetRowPacket> resultSet = new LinkedList();
+        byte[] statementResult = channel.read();
+        if (statementResult[0] == (byte) 0xFF /* error */) {
+            byte[] bytes = Arrays.copyOfRange(statementResult, 1, statementResult.length);
+            ErrorPacket errorPacket = new ErrorPacket(bytes);
+            throw new ServerException(errorPacket.getErrorMessage(), errorPacket.getErrorCode(),
+                    errorPacket.getSqlState());
+        }
+        while ((channel.read())[0] != (byte) 0xFE /* eof */) { /* skip */ }
+        for (byte[] bytes; (bytes = channel.read())[0] != (byte) 0xFE /* eof */; ) {
+            resultSet.add(new ResultSetRowPacket(bytes));
+        }
+        return resultSet.toArray(new ResultSetRowPacket[0]);
+    }
+
+    private void closeChannel(final PacketChannel channel) throws IOException {
+        if (channel != null && channel.isOpen()) {
+            channel.close();
+        }
+    }
+
+    private void setConfig() {
+        if (null == tableMapEventByTableId) {
+            tableMapEventByTableId = new HashMap<>();
+        }
+
+        IdentityHashMap eventDataDeserializers = new IdentityHashMap();
+        if (null == eventDeserializer) {
+            this.eventDeserializer = new EventDeserializer(new EventHeaderV4Deserializer(), new NullEventDataDeserializer(), eventDataDeserializers, tableMapEventByTableId);
+        }
+
+        // Process event priority: RotateEvent > FormatDescriptionEvent > TableMapEvent > RowsEvent > XidEvent
+        eventDataDeserializers.put(EventType.ROTATE, new RotateEventDataDeserializer());
+        eventDataDeserializers.put(EventType.FORMAT_DESCRIPTION, new FormatDescriptionEventDataDeserializer());
+        eventDataDeserializers.put(EventType.TABLE_MAP, new TableMapEventDataDeserializer());
+        eventDataDeserializers.put(EventType.UPDATE_ROWS, new UpdateRowsEventDataDeserializer(tableMapEventByTableId));
+        eventDataDeserializers.put(EventType.WRITE_ROWS, new WriteRowsEventDataDeserializer(tableMapEventByTableId));
+        eventDataDeserializers.put(EventType.DELETE_ROWS, new DeleteRowsEventDataDeserializer(tableMapEventByTableId));
+        eventDataDeserializers.put(EventType.EXT_WRITE_ROWS, (new WriteDeserializer(tableMapEventByTableId)).setMayContainExtraInformation(true));
+        eventDataDeserializers.put(EventType.EXT_UPDATE_ROWS, (new UpdateDeserializer(tableMapEventByTableId)).setMayContainExtraInformation(true));
+        eventDataDeserializers.put(EventType.EXT_DELETE_ROWS, (new DeleteDeserializer(tableMapEventByTableId)).setMayContainExtraInformation(true));
+        eventDataDeserializers.put(EventType.XID, new XidEventDataDeserializer());
+
+        if (enableDDL) {
+            eventDataDeserializers.put(EventType.INTVAR, new IntVarEventDataDeserializer());
+            eventDataDeserializers.put(EventType.QUERY, new QueryEventDataDeserializer());
+            eventDataDeserializers.put(EventType.ROWS_QUERY, new RowsQueryEventDataDeserializer());
+            eventDataDeserializers.put(EventType.GTID, new GtidEventDataDeserializer());
+            eventDataDeserializers.put(EventType.PREVIOUS_GTIDS, new PreviousGtidSetDeserializer());
+            eventDataDeserializers.put(EventType.XA_PREPARE, new XAPrepareEventDataDeserializer());
+        }
+
+    }
+
+    private void notifyEventListeners(Event event) {
+        if (event.getData() instanceof EventDeserializer.EventDataWrapper) {
+            event = new Event(event.getHeader(), ((EventDeserializer.EventDataWrapper) event.getData()).getExternal());
+        }
+        for (BinaryLogRemoteClient.EventListener eventListener : eventListeners) {
+            try {
+                eventListener.onEvent(event);
+            } catch (Exception e) {
+                if (logger.isWarnEnabled()) {
+                    logger.warn(eventListener + " choked on " + event, e);
+                }
+            }
+        }
+    }
+
+    private long randomPort(long serverId) throws IOException {
+        if (0 == serverId) {
+            ServerSocket serverSocket = null;
+            try {
+                serverSocket = new ServerSocket(0);
+                return serverSocket.getLocalPort();
+            } finally {
+                if (null != serverSocket) {
+                    serverSocket.close();
+                }
+            }
+        }
+        return serverId;
+    }
+
+    private void spawnWorkerThread() {
+        this.worker = new Thread(() -> listenForEventPackets(channel));
+        this.worker.setDaemon(false);
+        this.workerThreadName = new StringBuilder("binlog-parser-").append(createClientId()).toString();
+        this.worker.setName(workerThreadName);
+        this.worker.start();
+    }
+
+    private void spawnKeepAliveThread() {
+        long keepAliveInterval = TimeUnit.SECONDS.toMillis(30);
+        String clientId = createClientId();
+        this.keepAlive = new Thread(() -> {
+            while (connected) {
+                try {
+                    Thread.sleep(keepAliveInterval);
+                } catch (InterruptedException e) {
+                    // expected in case of disconnect
+                }
+                boolean connectionLost = false;
+                try {
+                    channel.write(new PingCommand());
+                } catch (IOException e) {
+                    connectionLost = true;
+                }
+                if (connectionLost) {
+                    if (connected) {
+                        String error = String.format("keepalive: Trying to restore lost connection to %s", clientId);
+                        logger.info(error);
+                        try {
+                            lifecycleListeners.forEach(listener -> listener.onCommunicationFailure(this, new ConnectorException(error)));
+                        } catch (Exception e) {
+                            logger.warn("keepalive error", e);
+                        }
+                    }
+                    break;
+                }
+            }
+        });
+        this.keepAlive.setDaemon(false);
+        this.keepAlive.setName(new StringBuilder("binlog-keepalive-").append(clientId).toString());
+        this.keepAlive.start();
+    }
+
+    @Override
+    public String getBinlogFilename() {
+        return binlogFilename;
+    }
+
+    @Override
+    public void setBinlogFilename(String binlogFilename) {
+        this.binlogFilename = binlogFilename;
+    }
+
+    @Override
+    public long getBinlogPosition() {
+        return binlogPosition;
+    }
+
+    @Override
+    public void setBinlogPosition(long binlogPosition) {
+        this.binlogPosition = binlogPosition;
+    }
+
+    @Override
+    public EventDeserializer getEventDeserializer() {
+        return eventDeserializer;
+    }
+
+    @Override
+    public void setEventDeserializer(EventDeserializer eventDeserializer) {
+        if (eventDeserializer == null) {
+            throw new IllegalArgumentException("Event deserializer cannot be NULL");
+        }
+        this.eventDeserializer = eventDeserializer;
+    }
+
+    @Override
+    public Map<Long, TableMapEventData> getTableMapEventByTableId() {
+        return tableMapEventByTableId;
+    }
+
+    @Override
+    public void setTableMapEventByTableId(Map<Long, TableMapEventData> tableMapEventByTableId) {
+        this.tableMapEventByTableId = tableMapEventByTableId;
+    }
+
+    public boolean isEnableDDL() {
+        return enableDDL;
+    }
+
+    public void setEnableDDL(boolean enableDDL) {
+        this.enableDDL = enableDDL;
+    }
+
+    @Override
+    public String getWorkerThreadName() {
+        return workerThreadName;
+    }
+
+    public SSLMode getSSLMode() {
+        return sslMode;
+    }
+
+    public void setSSLMode(SSLMode sslMode) {
+        if (sslMode == null) {
+            throw new IllegalArgumentException("SSL mode cannot be NULL");
+        }
+        this.sslMode = sslMode;
+    }
+
+    /**
+     * @return GTID set. Note that this value changes with each received GTID event (provided client is in GTID mode).
+     * @see #setGtidSet(String)
+     */
+    public String getGtidSet() {
+        synchronized (gtidSetAccessLock) {
+            return gtidSet != null ? gtidSet.toString() : null;
+        }
+    }
+
+    /**
+     * @param gtidStr GTID set string (can be an empty string).
+     * <p>NOTE #1: Any value but null will switch BinaryLogRemoteClient into a GTID mode (this will also set binlogFilename
+     * to "" (provided it's null) forcing MySQL to send events starting from the oldest known binlog (keep in mind
+     * that connection will fail if gtid_purged is anything but empty (unless
+     * {@link #setGtidSetFallbackToPurged(boolean)} is set to true))).
+     * <p>NOTE #2: GTID set is automatically updated with each incoming GTID event (provided GTID mode is on).
+     * @see #getGtidSet()
+     * @see #setGtidSetFallbackToPurged(boolean)
+     */
+    public void setGtidSet(String gtidStr) {
+        if (gtidStr == null)
+            return;
+
+        this.gtidEnabled = true;
+
+        if (this.binlogFilename == null) {
+            this.binlogFilename = "";
+        }
+        synchronized (gtidSetAccessLock) {
+            if (!gtidStr.equals("")) {
+                if (MariadbGtidSet.isMariaGtidSet(gtidStr)) {
+                    this.gtidSet = new MariadbGtidSet(gtidStr);
+                } else {
+                    this.gtidSet = new GtidSet(gtidStr);
+                }
+            }
+        }
+    }
+
+    /**
+     * @see #setGtidSetFallbackToPurged(boolean)
+     */
+    public boolean isGtidSetFallbackToPurged() {
+        return gtidSetFallbackToPurged;
+    }
+
+    /**
+     * @param gtidSetFallbackToPurged true if gtid_purged should be used as a fallback when gtidSet is set to "" and MySQL server has purged
+     *                                some of the binary logs, false otherwise (default).
+     */
+    public void setGtidSetFallbackToPurged(boolean gtidSetFallbackToPurged) {
+        this.gtidSetFallbackToPurged = gtidSetFallbackToPurged;
+    }
+
+    /**
+     * @see #setUseBinlogFilenamePositionInGtidMode(boolean)
+     */
+    public boolean isUseBinlogFilenamePositionInGtidMode() {
+        return useBinlogFilenamePositionInGtidMode;
+    }
+
+    /**
+     * @param useBinlogFilenamePositionInGtidMode true if MySQL server should start streaming events from a given {@link
+     *                                            #getBinlogFilename()} and {@link #getBinlogPosition()} instead of "the oldest known
+     *                                            binlog" when {@link #getGtidSet()} is set, false otherwise (default).
+     */
+    public void setUseBinlogFilenamePositionInGtidMode(boolean useBinlogFilenamePositionInGtidMode) {
+        this.useBinlogFilenamePositionInGtidMode = useBinlogFilenamePositionInGtidMode;
+    }
+
+    public interface EventListener {
+
+        void onEvent(Event event);
+    }
+
+    public interface LifecycleListener {
+
+        /**
+         * Called once client has successfully logged in but before started to receive binlog events.
+         */
+        void onConnect(BinaryLogRemoteClient client);
+
+        /**
+         * It's guarantied to be called before {@link #onDisconnect(BinaryLogRemoteClient)}) in case of communication failure.
+         */
+        void onCommunicationFailure(BinaryLogRemoteClient client, Exception ex);
+
+        /**
+         * Called in case of failed event deserialization. Note this type of error does NOT cause client to disconnect. If you wish to stop
+         * receiving events you'll need to fire client.disconnect() manually.
+         */
+        void onEventDeserializationFailure(BinaryLogRemoteClient client, Exception ex);
+
+        /**
+         * Called upon disconnect (regardless of the reason).
+         */
+        void onDisconnect(BinaryLogRemoteClient client);
+    }
+
 }

+ 3 - 3
dbsyncer-listener/src/main/java/org/dbsyncer/listener/mysql/DqlMySQLExtractor.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/mysql/DqlMySQLListener.java

@@ -1,13 +1,13 @@
-package org.dbsyncer.listener.mysql;
+package org.dbsyncer.connector.mysql;
 
-import org.dbsyncer.listener.ChangedEvent;
+import org.dbsyncer.sdk.listener.ChangedEvent;
 
 /**
  * @author AE86
  * @version 1.0.0
  * @date 2022/5/28 22:02
  */
-public class DqlMySQLExtractor extends MySQLExtractor {
+public class DqlMySQLListener extends MySQLListener {
 
     @Override
     public void start() {

+ 16 - 1
dbsyncer-connector/src/main/java/org/dbsyncer/connector/mysql/MySQLConnector.java

@@ -1,9 +1,12 @@
 package org.dbsyncer.connector.mysql;
 
 import org.dbsyncer.common.util.StringUtil;
+import org.dbsyncer.connector.quartz.DatabaseQuartzListener;
 import org.dbsyncer.sdk.config.ReaderConfig;
-import org.dbsyncer.sdk.constant.DatabaseConstant;
 import org.dbsyncer.sdk.connector.database.AbstractDatabaseConnector;
+import org.dbsyncer.sdk.constant.DatabaseConstant;
+import org.dbsyncer.sdk.enums.ListenerTypeEnum;
+import org.dbsyncer.sdk.listener.Listener;
 import org.dbsyncer.sdk.model.PageSql;
 import org.dbsyncer.sdk.util.PrimaryKeyUtil;
 import org.slf4j.Logger;
@@ -24,6 +27,18 @@ public final class MySQLConnector extends AbstractDatabaseConnector {
         return TYPE;
     }
 
+    @Override
+    public Listener getListener(String listenerType) {
+        if (ListenerTypeEnum.isTiming(listenerType)) {
+            return new DatabaseQuartzListener();
+        }
+
+        if (ListenerTypeEnum.isLog(listenerType)) {
+            return new MySQLListener();
+        }
+        return null;
+    }
+
     @Override
     public String buildSqlWithQuotation() {
         return "`";

+ 358 - 341
dbsyncer-listener/src/main/java/org/dbsyncer/listener/mysql/MySQLExtractor.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/mysql/MySQLListener.java

@@ -1,342 +1,359 @@
-package org.dbsyncer.listener.mysql;
-
-import com.github.shyiko.mysql.binlog.event.DeleteRowsEventData;
-import com.github.shyiko.mysql.binlog.event.Event;
-import com.github.shyiko.mysql.binlog.event.EventHeader;
-import com.github.shyiko.mysql.binlog.event.EventHeaderV4;
-import com.github.shyiko.mysql.binlog.event.EventType;
-import com.github.shyiko.mysql.binlog.event.QueryEventData;
-import com.github.shyiko.mysql.binlog.event.RotateEventData;
-import com.github.shyiko.mysql.binlog.event.TableMapEventData;
-import com.github.shyiko.mysql.binlog.event.UpdateRowsEventData;
-import com.github.shyiko.mysql.binlog.event.WriteRowsEventData;
-import com.github.shyiko.mysql.binlog.network.ServerException;
-import net.sf.jsqlparser.JSQLParserException;
-import net.sf.jsqlparser.parser.CCJSqlParserUtil;
-import net.sf.jsqlparser.statement.alter.Alter;
-import org.dbsyncer.listener.model.ChangedOffset;
-import org.dbsyncer.listener.event.DDLChangedEvent;
-import org.dbsyncer.listener.event.RowChangedEvent;
-import org.dbsyncer.common.util.StringUtil;
-import org.dbsyncer.sdk.config.DatabaseConfig;
-import org.dbsyncer.sdk.constant.ConnectorConstant;
-import org.dbsyncer.sdk.util.DatabaseUtil;
-import org.dbsyncer.listener.AbstractDatabaseExtractor;
-import org.dbsyncer.listener.ListenerException;
-import org.dbsyncer.listener.config.Host;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.util.Assert;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.regex.Matcher;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import static java.util.regex.Pattern.compile;
-
-/**
- * @version 1.0.0
- * @Author AE86
- * @Date 2020-05-12 21:14
- */
-public class MySQLExtractor extends AbstractDatabaseExtractor {
-
-    private final Logger logger = LoggerFactory.getLogger(getClass());
-
-    private final String BINLOG_FILENAME = "fileName";
-    private final String BINLOG_POSITION = "position";
-    private final int RETRY_TIMES = 10;
-    private final int MASTER = 0;
-    private Map<Long, TableMapEventData> tables = new HashMap<>();
-    private BinaryLogClient client;
-    private List<Host> cluster;
-    private String database;
-    private final Lock connectLock = new ReentrantLock();
-    private volatile boolean connected;
-    private volatile boolean recovery;
-
-    @Override
-    public void start() {
-        try {
-            connectLock.lock();
-            if (connected) {
-                logger.error("MySQLExtractor is already started");
-                return;
-            }
-            run();
-            connected = true;
-        } catch (Exception e) {
-            logger.error("启动失败:{}", e.getMessage());
-            throw new ListenerException(e);
-        } finally {
-            connectLock.unlock();
-        }
-    }
-
-    @Override
-    public void close() {
-        try {
-            connectLock.lock();
-            connected = false;
-            if (null != client) {
-                client.disconnect();
-            }
-        } catch (Exception e) {
-            logger.error("关闭失败:{}", e.getMessage());
-        } finally {
-            connectLock.unlock();
-        }
-    }
-
-    @Override
-    public void refreshEvent(ChangedOffset offset) {
-        refreshSnapshot(offset.getNextFileName(), (Long) offset.getPosition());
-    }
-
-    private void run() throws Exception {
-        final DatabaseConfig config = (DatabaseConfig) connectorConfig;
-        if (StringUtil.isBlank(config.getUrl())) {
-            throw new ListenerException("url is invalid");
-        }
-        database = DatabaseUtil.getDatabaseName(config.getUrl());
-        cluster = readNodes(config.getUrl());
-        Assert.notEmpty(cluster, "MySQL连接地址有误.");
-
-        final Host host = cluster.get(MASTER);
-        final String username = config.getUsername();
-        final String password = config.getPassword();
-        boolean containsPos = snapshot.containsKey(BINLOG_POSITION);
-        client = new BinaryLogRemoteClient(host.getIp(), host.getPort(), username, password);
-        client.setEnableDDL(true);
-        client.setBinlogFilename(snapshot.get(BINLOG_FILENAME));
-        client.setBinlogPosition(containsPos ? Long.parseLong(snapshot.get(BINLOG_POSITION)) : 0);
-        client.setTableMapEventByTableId(tables);
-        client.registerEventListener(new MysqlEventListener());
-        client.registerLifecycleListener(new MysqlLifecycleListener());
-
-        client.connect();
-
-        if (!containsPos) {
-            refreshSnapshot(client.getBinlogFilename(), client.getBinlogPosition());
-            super.forceFlushEvent();
-        }
-    }
-
-    private List<Host> readNodes(String url) {
-        Matcher matcher = compile("(//)(?!(/)).+?(/)").matcher(url);
-        while (matcher.find()) {
-            url = matcher.group(0);
-            break;
-        }
-        url = StringUtil.replace(url, "/", "");
-
-        List<Host> cluster = new ArrayList<>();
-        String[] arr = StringUtil.split(url, ",");
-        int size = arr.length;
-        for (int i = 0; i < size; i++) {
-            String[] host = StringUtil.split(arr[i], ":");
-            if (2 == host.length) {
-                cluster.add(new Host(host[0], Integer.parseInt(host[1])));
-            }
-        }
-        return cluster;
-    }
-
-    private void reStart() {
-        try {
-            connectLock.lock();
-            if (recovery) {
-                return;
-            }
-            recovery = true;
-        } finally {
-            connectLock.unlock();
-        }
-
-        for (int i = 1; i <= RETRY_TIMES; i++) {
-            try {
-                if (null != client) {
-                    client.disconnect();
-                }
-                run();
-
-                errorEvent(new ListenerException(String.format("重启成功, %s", client.getWorkerThreadName())));
-                logger.error("第{}次重启成功, ThreadName:{} ", i, client.getWorkerThreadName());
-                recovery = false;
-                break;
-            } catch (Exception e) {
-                logger.error("第{}次重启异常, ThreadName:{}, {}", i, client.getWorkerThreadName(), e.getMessage());
-                // 无法连接,关闭任务
-                if (i == RETRY_TIMES) {
-                    errorEvent(new ListenerException(String.format("重启异常, %s, %s", client.getWorkerThreadName(), e.getMessage())));
-                }
-            }
-            try {
-                TimeUnit.SECONDS.sleep(i * 2);
-            } catch (InterruptedException e) {
-                logger.error(e.getMessage());
-            }
-        }
-    }
-
-    private void refresh(EventHeader header) {
-        EventHeaderV4 eventHeaderV4 = (EventHeaderV4) header;
-        refresh(null, eventHeaderV4.getNextPosition());
-    }
-
-    private void refresh(String binlogFilename, long nextPosition) {
-        if (StringUtil.isNotBlank(binlogFilename)) {
-            client.setBinlogFilename(binlogFilename);
-        }
-        if (0 < nextPosition) {
-            client.setBinlogPosition(nextPosition);
-        }
-    }
-
-    private void refreshSnapshot(String binlogFilename, long nextPosition) {
-        snapshot.put(BINLOG_FILENAME, binlogFilename);
-        snapshot.put(BINLOG_POSITION, String.valueOf(nextPosition));
-    }
-
-    final class MysqlLifecycleListener implements BinaryLogRemoteClient.LifecycleListener {
-
-        @Override
-        public void onConnect(BinaryLogRemoteClient client) {
-            // 记录binlog增量点
-            refresh(client.getBinlogFilename(), client.getBinlogPosition());
-        }
-
-        @Override
-        public void onCommunicationFailure(BinaryLogRemoteClient client, Exception e) {
-            if (!connected) {
-                return;
-            }
-            logger.error(e.getMessage());
-            /**
-             * e:
-             * case1> Due to the automatic expiration and deletion mechanism of MySQL binlog files, the binlog file cannot be found.
-             * case2> Got fatal error 1236 from master when reading data from binary log.
-             * case3> Log event entry exceeded max_allowed_packet; Increase max_allowed_packet on master.
-             */
-            if (e instanceof ServerException) {
-                ServerException serverException = (ServerException) e;
-                if (serverException.getErrorCode() == 1236) {
-                    close();
-                    String log = String.format("线程[%s]执行异常。由于MySQL配置了过期binlog文件自动删除机制,已无法找到原binlog文件%s。建议先保存驱动(加载最新的binlog文件),再启动驱动。",
-                            client.getWorkerThreadName(),
-                            client.getBinlogFilename());
-                    errorEvent(new ListenerException(log));
-                    return;
-                }
-            }
-
-            reStart();
-        }
-
-        @Override
-        public void onEventDeserializationFailure(BinaryLogRemoteClient client, Exception ex) {
-        }
-
-        @Override
-        public void onDisconnect(BinaryLogRemoteClient client) {
-        }
-
-    }
-
-    final class MysqlEventListener implements BinaryLogRemoteClient.EventListener {
-
-        @Override
-        public void onEvent(Event event) {
-            // ROTATE > FORMAT_DESCRIPTION > TABLE_MAP > WRITE_ROWS > UPDATE_ROWS > DELETE_ROWS > XID
-            EventHeader header = event.getHeader();
-            if (header.getEventType() == EventType.XID) {
-                refresh(header);
-                return;
-            }
-
-            if (EventType.isUpdate(header.getEventType())) {
-                refresh(header);
-                UpdateRowsEventData data = event.getData();
-                if (isFilterTable(data.getTableId())) {
-                    data.getRows().forEach(m -> {
-                        List<Object> after = Stream.of(m.getValue()).collect(Collectors.toList());
-                        sendChangedEvent(new RowChangedEvent(getTableName(data.getTableId()), ConnectorConstant.OPERTION_UPDATE, after, client.getBinlogFilename(), client.getBinlogPosition()));
-                    });
-                }
-                return;
-            }
-            if (EventType.isWrite(header.getEventType())) {
-                refresh(header);
-                WriteRowsEventData data = event.getData();
-                if (isFilterTable(data.getTableId())) {
-                    data.getRows().forEach(m -> {
-                        List<Object> after = Stream.of(m).collect(Collectors.toList());
-                        sendChangedEvent(new RowChangedEvent(getTableName(data.getTableId()), ConnectorConstant.OPERTION_INSERT, after, client.getBinlogFilename(), client.getBinlogPosition()));
-                    });
-                }
-                return;
-            }
-            if (EventType.isDelete(header.getEventType())) {
-                refresh(header);
-                DeleteRowsEventData data = event.getData();
-                if (isFilterTable(data.getTableId())) {
-                    data.getRows().forEach(m -> {
-                        List<Object> before = Stream.of(m).collect(Collectors.toList());
-                        sendChangedEvent(new RowChangedEvent(getTableName(data.getTableId()), ConnectorConstant.OPERTION_DELETE, before, client.getBinlogFilename(), client.getBinlogPosition()));
-                    });
-                }
-                return;
-            }
-
-            if (client.isEnableDDL() && EventType.QUERY == header.getEventType()) {
-                refresh(header);
-                parseDDL(event.getData());
-                return;
-            }
-
-            // 切换binlog
-            if (header.getEventType() == EventType.ROTATE) {
-                RotateEventData data = event.getData();
-                refresh(data.getBinlogFilename(), data.getBinlogPosition());
-            }
-        }
-
-        private void parseDDL(QueryEventData data) {
-            if (StringUtil.startsWith(data.getSql(), ConnectorConstant.OPERTION_ALTER)) {
-                try {
-                    // ALTER TABLE `test`.`my_user` MODIFY COLUMN `name` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL AFTER `id`
-                    Alter alter = (Alter) CCJSqlParserUtil.parse(data.getSql());
-                    String tableName = StringUtil.replace(alter.getTable().getName(),"`","");
-                    if (isFilterTable(data.getDatabase(), tableName)) {
-                        logger.info("sql:{}", data.getSql());
-                        changeEvent(new DDLChangedEvent(data.getDatabase(), tableName, ConnectorConstant.OPERTION_ALTER, data.getSql(), client.getBinlogFilename(), client.getBinlogPosition()));
-                    }
-                } catch (JSQLParserException e) {
-                    logger.error("不支持ddl sql,支持标准的sql格式,请查看文档https://gitee.com/ghi/dbsyncer/wikis/%E5%BF%AB%E9%80%9F%E4%BA%86%E8%A7%A3/%E8%A1%A8%E7%BB%93%E6%9E%84%E5%90%8C%E6%AD%A5");
-                }
-            }
-        }
-
-        private String getTableName(long tableId) {
-            return tables.get(tableId).getTable();
-        }
-
-        private boolean isFilterTable(long tableId) {
-            final TableMapEventData tableMap = tables.get(tableId);
-            return isFilterTable(tableMap.getDatabase(), tableMap.getTable());
-        }
-
-        private boolean isFilterTable(String dbName, String tableName) {
-            return StringUtil.equalsIgnoreCase(database, dbName) && filterTable.contains(tableName);
-        }
-
-    }
-
+package org.dbsyncer.connector.mysql;
+
+import com.github.shyiko.mysql.binlog.event.DeleteRowsEventData;
+import com.github.shyiko.mysql.binlog.event.Event;
+import com.github.shyiko.mysql.binlog.event.EventHeader;
+import com.github.shyiko.mysql.binlog.event.EventHeaderV4;
+import com.github.shyiko.mysql.binlog.event.EventType;
+import com.github.shyiko.mysql.binlog.event.QueryEventData;
+import com.github.shyiko.mysql.binlog.event.RotateEventData;
+import com.github.shyiko.mysql.binlog.event.TableMapEventData;
+import com.github.shyiko.mysql.binlog.event.UpdateRowsEventData;
+import com.github.shyiko.mysql.binlog.event.WriteRowsEventData;
+import com.github.shyiko.mysql.binlog.network.ServerException;
+import net.sf.jsqlparser.JSQLParserException;
+import net.sf.jsqlparser.parser.CCJSqlParserUtil;
+import net.sf.jsqlparser.statement.alter.Alter;
+import org.dbsyncer.common.util.StringUtil;
+import org.dbsyncer.connector.AbstractDatabaseListener;
+import org.dbsyncer.connector.ConnectorException;
+import org.dbsyncer.sdk.config.DatabaseConfig;
+import org.dbsyncer.sdk.constant.ConnectorConstant;
+import org.dbsyncer.sdk.listener.event.DDLChangedEvent;
+import org.dbsyncer.sdk.listener.event.RowChangedEvent;
+import org.dbsyncer.sdk.model.ChangedOffset;
+import org.dbsyncer.sdk.util.DatabaseUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.Assert;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.regex.Matcher;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static java.util.regex.Pattern.compile;
+
+/**
+ * @version 1.0.0
+ * @Author AE86
+ * @Date 2020-05-12 21:14
+ */
+public class MySQLListener extends AbstractDatabaseListener {
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    private final String BINLOG_FILENAME = "fileName";
+    private final String BINLOG_POSITION = "position";
+    private final int RETRY_TIMES = 10;
+    private final int MASTER = 0;
+    private Map<Long, TableMapEventData> tables = new HashMap<>();
+    private BinaryLogClient client;
+    private List<Host> cluster;
+    private String database;
+    private final Lock connectLock = new ReentrantLock();
+    private volatile boolean connected;
+    private volatile boolean recovery;
+
+    @Override
+    public void start() {
+        try {
+            connectLock.lock();
+            if (connected) {
+                logger.error("MySQLExtractor is already started");
+                return;
+            }
+            run();
+            connected = true;
+        } catch (Exception e) {
+            logger.error("启动失败:{}", e.getMessage());
+            throw new ConnectorException(e);
+        } finally {
+            connectLock.unlock();
+        }
+    }
+
+    @Override
+    public void close() {
+        try {
+            connectLock.lock();
+            connected = false;
+            if (null != client) {
+                client.disconnect();
+            }
+        } catch (Exception e) {
+            logger.error("关闭失败:{}", e.getMessage());
+        } finally {
+            connectLock.unlock();
+        }
+    }
+
+    @Override
+    public void refreshEvent(ChangedOffset offset) {
+        refreshSnapshot(offset.getNextFileName(), (Long) offset.getPosition());
+    }
+
+    private void run() throws Exception {
+        final DatabaseConfig config = (DatabaseConfig) connectorConfig;
+        if (StringUtil.isBlank(config.getUrl())) {
+            throw new ConnectorException("url is invalid");
+        }
+        database = DatabaseUtil.getDatabaseName(config.getUrl());
+        cluster = readNodes(config.getUrl());
+        Assert.notEmpty(cluster, "MySQL连接地址有误.");
+
+        final Host host = cluster.get(MASTER);
+        final String username = config.getUsername();
+        final String password = config.getPassword();
+        boolean containsPos = snapshot.containsKey(BINLOG_POSITION);
+        client = new BinaryLogRemoteClient(host.getIp(), host.getPort(), username, password);
+        client.setEnableDDL(true);
+        client.setBinlogFilename(snapshot.get(BINLOG_FILENAME));
+        client.setBinlogPosition(containsPos ? Long.parseLong(snapshot.get(BINLOG_POSITION)) : 0);
+        client.setTableMapEventByTableId(tables);
+        client.registerEventListener(new MysqlEventListener());
+        client.registerLifecycleListener(new MysqlLifecycleListener());
+
+        client.connect();
+
+        if (!containsPos) {
+            refreshSnapshot(client.getBinlogFilename(), client.getBinlogPosition());
+            super.forceFlushEvent();
+        }
+    }
+
+    private List<Host> readNodes(String url) {
+        Matcher matcher = compile("(//)(?!(/)).+?(/)").matcher(url);
+        while (matcher.find()) {
+            url = matcher.group(0);
+            break;
+        }
+        url = StringUtil.replace(url, "/", "");
+
+        List<Host> cluster = new ArrayList<>();
+        String[] arr = StringUtil.split(url, ",");
+        int size = arr.length;
+        for (int i = 0; i < size; i++) {
+            String[] host = StringUtil.split(arr[i], ":");
+            if (2 == host.length) {
+                cluster.add(new Host(host[0], Integer.parseInt(host[1])));
+            }
+        }
+        return cluster;
+    }
+
+    private void reStart() {
+        try {
+            connectLock.lock();
+            if (recovery) {
+                return;
+            }
+            recovery = true;
+        } finally {
+            connectLock.unlock();
+        }
+
+        for (int i = 1; i <= RETRY_TIMES; i++) {
+            try {
+                if (null != client) {
+                    client.disconnect();
+                }
+                run();
+
+                errorEvent(new ConnectorException(String.format("重启成功, %s", client.getWorkerThreadName())));
+                logger.error("第{}次重启成功, ThreadName:{} ", i, client.getWorkerThreadName());
+                recovery = false;
+                break;
+            } catch (Exception e) {
+                logger.error("第{}次重启异常, ThreadName:{}, {}", i, client.getWorkerThreadName(), e.getMessage());
+                // 无法连接,关闭任务
+                if (i == RETRY_TIMES) {
+                    errorEvent(new ConnectorException(String.format("重启异常, %s, %s", client.getWorkerThreadName(), e.getMessage())));
+                }
+            }
+            try {
+                TimeUnit.SECONDS.sleep(i * 2);
+            } catch (InterruptedException e) {
+                logger.error(e.getMessage());
+            }
+        }
+    }
+
+    private void refresh(EventHeader header) {
+        EventHeaderV4 eventHeaderV4 = (EventHeaderV4) header;
+        refresh(null, eventHeaderV4.getNextPosition());
+    }
+
+    private void refresh(String binlogFilename, long nextPosition) {
+        if (StringUtil.isNotBlank(binlogFilename)) {
+            client.setBinlogFilename(binlogFilename);
+        }
+        if (0 < nextPosition) {
+            client.setBinlogPosition(nextPosition);
+        }
+    }
+
+    private void refreshSnapshot(String binlogFilename, long nextPosition) {
+        snapshot.put(BINLOG_FILENAME, binlogFilename);
+        snapshot.put(BINLOG_POSITION, String.valueOf(nextPosition));
+    }
+
+    final class Host {
+        private String ip;
+        private int port;
+
+        public Host(String ip, int port) {
+            this.ip = ip;
+            this.port = port;
+        }
+
+        public String getIp() {
+            return ip;
+        }
+
+        public int getPort() {
+            return port;
+        }
+    }
+
+    final class MysqlLifecycleListener implements BinaryLogRemoteClient.LifecycleListener {
+
+        @Override
+        public void onConnect(BinaryLogRemoteClient client) {
+            // 记录binlog增量点
+            refresh(client.getBinlogFilename(), client.getBinlogPosition());
+        }
+
+        @Override
+        public void onCommunicationFailure(BinaryLogRemoteClient client, Exception e) {
+            if (!connected) {
+                return;
+            }
+            logger.error(e.getMessage());
+            /**
+             * e:
+             * case1> Due to the automatic expiration and deletion mechanism of MySQL binlog files, the binlog file cannot be found.
+             * case2> Got fatal error 1236 from master when reading data from binary log.
+             * case3> Log event entry exceeded max_allowed_packet; Increase max_allowed_packet on master.
+             */
+            if (e instanceof ServerException) {
+                ServerException serverException = (ServerException) e;
+                if (serverException.getErrorCode() == 1236) {
+                    close();
+                    String log = String.format("线程[%s]执行异常。由于MySQL配置了过期binlog文件自动删除机制,已无法找到原binlog文件%s。建议先保存驱动(加载最新的binlog文件),再启动驱动。",
+                            client.getWorkerThreadName(),
+                            client.getBinlogFilename());
+                    errorEvent(new ConnectorException(log));
+                    return;
+                }
+            }
+
+            reStart();
+        }
+
+        @Override
+        public void onEventDeserializationFailure(BinaryLogRemoteClient client, Exception ex) {
+        }
+
+        @Override
+        public void onDisconnect(BinaryLogRemoteClient client) {
+        }
+
+    }
+
+    final class MysqlEventListener implements BinaryLogRemoteClient.EventListener {
+
+        @Override
+        public void onEvent(Event event) {
+            // ROTATE > FORMAT_DESCRIPTION > TABLE_MAP > WRITE_ROWS > UPDATE_ROWS > DELETE_ROWS > XID
+            EventHeader header = event.getHeader();
+            if (header.getEventType() == EventType.XID) {
+                refresh(header);
+                return;
+            }
+
+            if (EventType.isUpdate(header.getEventType())) {
+                refresh(header);
+                UpdateRowsEventData data = event.getData();
+                if (isFilterTable(data.getTableId())) {
+                    data.getRows().forEach(m -> {
+                        List<Object> after = Stream.of(m.getValue()).collect(Collectors.toList());
+                        sendChangedEvent(new RowChangedEvent(getTableName(data.getTableId()), ConnectorConstant.OPERTION_UPDATE, after, client.getBinlogFilename(), client.getBinlogPosition()));
+                    });
+                }
+                return;
+            }
+            if (EventType.isWrite(header.getEventType())) {
+                refresh(header);
+                WriteRowsEventData data = event.getData();
+                if (isFilterTable(data.getTableId())) {
+                    data.getRows().forEach(m -> {
+                        List<Object> after = Stream.of(m).collect(Collectors.toList());
+                        sendChangedEvent(new RowChangedEvent(getTableName(data.getTableId()), ConnectorConstant.OPERTION_INSERT, after, client.getBinlogFilename(), client.getBinlogPosition()));
+                    });
+                }
+                return;
+            }
+            if (EventType.isDelete(header.getEventType())) {
+                refresh(header);
+                DeleteRowsEventData data = event.getData();
+                if (isFilterTable(data.getTableId())) {
+                    data.getRows().forEach(m -> {
+                        List<Object> before = Stream.of(m).collect(Collectors.toList());
+                        sendChangedEvent(new RowChangedEvent(getTableName(data.getTableId()), ConnectorConstant.OPERTION_DELETE, before, client.getBinlogFilename(), client.getBinlogPosition()));
+                    });
+                }
+                return;
+            }
+
+            if (client.isEnableDDL() && EventType.QUERY == header.getEventType()) {
+                refresh(header);
+                parseDDL(event.getData());
+                return;
+            }
+
+            // 切换binlog
+            if (header.getEventType() == EventType.ROTATE) {
+                RotateEventData data = event.getData();
+                refresh(data.getBinlogFilename(), data.getBinlogPosition());
+            }
+        }
+
+        private void parseDDL(QueryEventData data) {
+            if (StringUtil.startsWith(data.getSql(), ConnectorConstant.OPERTION_ALTER)) {
+                try {
+                    // ALTER TABLE `test`.`my_user` MODIFY COLUMN `name` varchar(128) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL AFTER `id`
+                    Alter alter = (Alter) CCJSqlParserUtil.parse(data.getSql());
+                    String tableName = StringUtil.replace(alter.getTable().getName(), "`", "");
+                    if (isFilterTable(data.getDatabase(), tableName)) {
+                        logger.info("sql:{}", data.getSql());
+                        changeEvent(new DDLChangedEvent(data.getDatabase(), tableName, ConnectorConstant.OPERTION_ALTER, data.getSql(), client.getBinlogFilename(), client.getBinlogPosition()));
+                    }
+                } catch (JSQLParserException e) {
+                    logger.error("不支持ddl sql,支持标准的sql格式,请查看文档https://gitee.com/ghi/dbsyncer/wikis/%E5%BF%AB%E9%80%9F%E4%BA%86%E8%A7%A3/%E8%A1%A8%E7%BB%93%E6%9E%84%E5%90%8C%E6%AD%A5");
+                }
+            }
+        }
+
+        private String getTableName(long tableId) {
+            return tables.get(tableId).getTable();
+        }
+
+        private boolean isFilterTable(long tableId) {
+            final TableMapEventData tableMap = tables.get(tableId);
+            return isFilterTable(tableMap.getDatabase(), tableMap.getTable());
+        }
+
+        private boolean isFilterTable(String dbName, String tableName) {
+            return StringUtil.equalsIgnoreCase(database, dbName) && filterTable.contains(tableName);
+        }
+
+    }
+
 }

+ 1 - 1
dbsyncer-listener/src/main/java/org/dbsyncer/listener/mysql/deserializer/DatetimeV2Deserialize.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/mysql/deserializer/DatetimeV2Deserialize.java

@@ -1,4 +1,4 @@
-package org.dbsyncer.listener.mysql.deserializer;
+package org.dbsyncer.connector.mysql.deserializer;
 
 import com.github.shyiko.mysql.binlog.io.ByteArrayInputStream;
 

+ 1 - 1
dbsyncer-listener/src/main/java/org/dbsyncer/listener/mysql/deserializer/DeleteDeserializer.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/mysql/deserializer/DeleteDeserializer.java

@@ -1,4 +1,4 @@
-package org.dbsyncer.listener.mysql.deserializer;
+package org.dbsyncer.connector.mysql.deserializer;
 
 import com.github.shyiko.mysql.binlog.event.TableMapEventData;
 import com.github.shyiko.mysql.binlog.event.deserialization.DeleteRowsEventDataDeserializer;

+ 1 - 1
dbsyncer-listener/src/main/java/org/dbsyncer/listener/mysql/deserializer/JsonBinaryDeserialize.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/mysql/deserializer/JsonBinaryDeserialize.java

@@ -1,4 +1,4 @@
-package org.dbsyncer.listener.mysql.deserializer;
+package org.dbsyncer.connector.mysql.deserializer;
 
 import com.github.shyiko.mysql.binlog.event.deserialization.json.JsonBinary;
 import com.github.shyiko.mysql.binlog.io.ByteArrayInputStream;

+ 1 - 1
dbsyncer-listener/src/main/java/org/dbsyncer/listener/mysql/deserializer/UpdateDeserializer.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/mysql/deserializer/UpdateDeserializer.java

@@ -1,4 +1,4 @@
-package org.dbsyncer.listener.mysql.deserializer;
+package org.dbsyncer.connector.mysql.deserializer;
 
 import com.github.shyiko.mysql.binlog.event.TableMapEventData;
 import com.github.shyiko.mysql.binlog.event.deserialization.UpdateRowsEventDataDeserializer;

+ 1 - 1
dbsyncer-listener/src/main/java/org/dbsyncer/listener/mysql/deserializer/WriteDeserializer.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/mysql/deserializer/WriteDeserializer.java

@@ -1,4 +1,4 @@
-package org.dbsyncer.listener.mysql.deserializer;
+package org.dbsyncer.connector.mysql.deserializer;
 
 import com.github.shyiko.mysql.binlog.event.TableMapEventData;
 import com.github.shyiko.mysql.binlog.event.deserialization.WriteRowsEventDataDeserializer;

+ 28 - 28
dbsyncer-listener/src/main/java/org/dbsyncer/listener/oracle/event/DCNEvent.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/oracle/DCNEvent.java

@@ -1,29 +1,29 @@
-/**
- * DBSyncer Copyright 2019-2024 All Rights Reserved.
- */
-package org.dbsyncer.listener.oracle.event;
-
-public final class DCNEvent {
-
-    private String tableName;
-    private String rowId;
-    private int code;
-
-    public DCNEvent(String tableName, String rowId, int code) {
-        this.tableName = tableName;
-        this.rowId = rowId;
-        this.code = code;
-    }
-
-    public String getTableName() {
-        return tableName;
-    }
-
-    public String getRowId() {
-        return rowId;
-    }
-
-    public int getCode() {
-        return code;
-    }
+/**
+ * DBSyncer Copyright 2019-2024 All Rights Reserved.
+ */
+package org.dbsyncer.connector.oracle;
+
+public final class DCNEvent {
+
+    private String tableName;
+    private String rowId;
+    private int code;
+
+    public DCNEvent(String tableName, String rowId, int code) {
+        this.tableName = tableName;
+        this.rowId = rowId;
+        this.code = code;
+    }
+
+    public String getTableName() {
+        return tableName;
+    }
+
+    public String getRowId() {
+        return rowId;
+    }
+
+    public int getCode() {
+        return code;
+    }
 }

+ 3 - 3
dbsyncer-listener/src/main/java/org/dbsyncer/listener/oracle/DqlOracleExtractor.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/oracle/DqlOracleListener.java

@@ -1,6 +1,6 @@
-package org.dbsyncer.listener.oracle;
+package org.dbsyncer.connector.oracle;
 
-import org.dbsyncer.listener.ChangedEvent;
+import org.dbsyncer.sdk.listener.ChangedEvent;
 import org.dbsyncer.sdk.model.Field;
 
 import java.util.List;
@@ -10,7 +10,7 @@ import java.util.List;
  * @version 1.0.0
  * @date 2022/5/29 22:44
  */
-public class DqlOracleExtractor extends OracleExtractor {
+public class DqlOracleListener extends OracleListener {
 
     @Override
     public void start() {

+ 15 - 0
dbsyncer-connector/src/main/java/org/dbsyncer/connector/oracle/OracleConnector.java

@@ -1,12 +1,15 @@
 package org.dbsyncer.connector.oracle;
 
 import org.dbsyncer.common.util.StringUtil;
+import org.dbsyncer.connector.quartz.DatabaseQuartzListener;
 import org.dbsyncer.sdk.config.CommandConfig;
 import org.dbsyncer.sdk.config.DatabaseConfig;
 import org.dbsyncer.sdk.config.ReaderConfig;
 import org.dbsyncer.sdk.connector.database.AbstractDatabaseConnector;
 import org.dbsyncer.sdk.constant.DatabaseConstant;
+import org.dbsyncer.sdk.enums.ListenerTypeEnum;
 import org.dbsyncer.sdk.enums.TableTypeEnum;
+import org.dbsyncer.sdk.listener.Listener;
 import org.dbsyncer.sdk.model.PageSql;
 import org.dbsyncer.sdk.model.Table;
 import org.dbsyncer.sdk.util.PrimaryKeyUtil;
@@ -35,6 +38,18 @@ public final class OracleConnector extends AbstractDatabaseConnector {
         return TYPE;
     }
 
+    @Override
+    public Listener getListener(String listenerType) {
+        if (ListenerTypeEnum.isTiming(listenerType)) {
+            return new DatabaseQuartzListener();
+        }
+
+        if (ListenerTypeEnum.isLog(listenerType)) {
+            return new OracleListener();
+        }
+        return null;
+    }
+
     @Override
     public String buildSqlWithQuotation() {
         return "\"";

+ 6 - 6
dbsyncer-listener/src/main/java/org/dbsyncer/listener/oracle/OracleExtractor.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/oracle/OracleListener.java

@@ -1,9 +1,9 @@
-package org.dbsyncer.listener.oracle;
+package org.dbsyncer.connector.oracle;
 
+import org.dbsyncer.connector.AbstractDatabaseListener;
+import org.dbsyncer.connector.ConnectorException;
+import org.dbsyncer.connector.oracle.dcn.DBChangeNotification;
 import org.dbsyncer.sdk.config.DatabaseConfig;
-import org.dbsyncer.listener.AbstractDatabaseExtractor;
-import org.dbsyncer.listener.ListenerException;
-import org.dbsyncer.listener.oracle.dcn.DBChangeNotification;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -12,7 +12,7 @@ import org.slf4j.LoggerFactory;
  * @Author AE86
  * @Date 2020-05-12 21:14
  */
-public class OracleExtractor extends AbstractDatabaseExtractor {
+public class OracleListener extends AbstractDatabaseListener {
 
     private final Logger logger = LoggerFactory.getLogger(getClass());
 
@@ -31,7 +31,7 @@ public class OracleExtractor extends AbstractDatabaseExtractor {
             client.start();
         } catch (Exception e) {
             logger.error("启动失败:{}", e.getMessage());
-            throw new ListenerException(e);
+            throw new ConnectorException(e);
         }
     }
 

+ 8 - 8
dbsyncer-listener/src/main/java/org/dbsyncer/listener/oracle/dcn/DBChangeNotification.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/oracle/dcn/DBChangeNotification.java

@@ -1,7 +1,7 @@
 /**
  * DBSyncer Copyright 2019-2024 All Rights Reserved.
  */
-package org.dbsyncer.listener.oracle.dcn;
+package org.dbsyncer.connector.oracle.dcn;
 
 import oracle.jdbc.OracleDriver;
 import oracle.jdbc.OracleStatement;
@@ -11,11 +11,11 @@ import oracle.jdbc.dcn.DatabaseChangeRegistration;
 import oracle.jdbc.dcn.RowChangeDescription;
 import oracle.jdbc.dcn.TableChangeDescription;
 import oracle.jdbc.driver.OracleConnection;
-import org.dbsyncer.listener.event.RowChangedEvent;
 import org.dbsyncer.common.util.StringUtil;
+import org.dbsyncer.connector.ConnectorException;
+import org.dbsyncer.connector.oracle.DCNEvent;
 import org.dbsyncer.sdk.constant.ConnectorConstant;
-import org.dbsyncer.listener.ListenerException;
-import org.dbsyncer.listener.oracle.event.DCNEvent;
+import org.dbsyncer.sdk.listener.event.RowChangedEvent;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.util.Assert;
@@ -248,7 +248,7 @@ public class DBChangeNotification {
         } catch (InvocationTargetException e) {
             logger.error(e.getMessage());
         }
-        throw new ListenerException(String.format("Can't invoke '%s'.", declaredMethod));
+        throw new ConnectorException(String.format("Can't invoke '%s'.", declaredMethod));
     }
 
     /**
@@ -265,7 +265,7 @@ public class DBChangeNotification {
         }
 
         // TNS
-        if(StringUtil.startsWith(url, "jdbc:oracle:thin:@(")){
+        if (StringUtil.startsWith(url, "jdbc:oracle:thin:@(")) {
             Object obj = invokeDCR(dcr, "getClientHost");
             return String.valueOf(obj);
         }
@@ -356,14 +356,14 @@ public class DBChangeNotification {
             }
 
             // 断线
-            if(eventType == DatabaseChangeEvent.EventType.SHUTDOWN){
+            if (eventType == DatabaseChangeEvent.EventType.SHUTDOWN) {
                 connected = false;
                 logger.error("连接中断,等待Oracle数据库重启中...");
                 return;
             }
 
             // 重启
-            if(eventType == DatabaseChangeEvent.EventType.STARTUP){
+            if (eventType == DatabaseChangeEvent.EventType.STARTUP) {
                 try {
                     conn = connect();
                     connected = true;

+ 18 - 18
dbsyncer-listener/src/main/java/org/dbsyncer/listener/oracle/dcn/RowEventListener.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/oracle/dcn/RowEventListener.java

@@ -1,19 +1,19 @@
-/**
- * DBSyncer Copyright 2019-2024 All Rights Reserved.
- */
-package org.dbsyncer.listener.oracle.dcn;
-
-import org.dbsyncer.listener.event.RowChangedEvent;
-
-/**
- * 行变更监听器
- *
- * @version 1.0.0
- * @Author AE86
- * @Date 2020-06-15 20:00
- */
-public interface RowEventListener {
-
-    void onEvents(RowChangedEvent rowChangedEvent);
-
+/**
+ * DBSyncer Copyright 2019-2024 All Rights Reserved.
+ */
+package org.dbsyncer.connector.oracle.dcn;
+
+import org.dbsyncer.sdk.listener.event.RowChangedEvent;
+
+/**
+ * 行变更监听器
+ *
+ * @version 1.0.0
+ * @Author AE86
+ * @Date 2020-06-15 20:00
+ */
+public interface RowEventListener {
+
+    void onEvents(RowChangedEvent rowChangedEvent);
+
 }

+ 3 - 3
dbsyncer-listener/src/main/java/org/dbsyncer/listener/postgresql/AbstractMessageDecoder.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/postgresql/AbstractMessageDecoder.java

@@ -1,8 +1,8 @@
-package org.dbsyncer.listener.postgresql;
+package org.dbsyncer.connector.postgresql;
 
 import org.dbsyncer.sdk.config.DatabaseConfig;
-import org.dbsyncer.listener.postgresql.column.PgColumnValue;
-import org.dbsyncer.listener.postgresql.enums.MessageTypeEnum;
+import org.dbsyncer.connector.postgresql.column.PgColumnValue;
+import org.dbsyncer.connector.postgresql.enums.MessageTypeEnum;
 import org.postgresql.replication.LogSequenceNumber;
 import org.postgresql.util.PGmoney;
 

+ 3 - 3
dbsyncer-listener/src/main/java/org/dbsyncer/listener/postgresql/DqlPostgreSQLExtractor.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/postgresql/DqlPostgreSQLListener.java

@@ -1,13 +1,13 @@
-package org.dbsyncer.listener.postgresql;
+package org.dbsyncer.connector.postgresql;
 
-import org.dbsyncer.listener.ChangedEvent;
+import org.dbsyncer.sdk.listener.ChangedEvent;
 
 /**
  * @author AE86
  * @version 1.0.0
  * @date 2022/5/29 22:44
  */
-public class DqlPostgreSQLExtractor extends PostgreSQLExtractor {
+public class DqlPostgreSQLListener extends PostgreSQLListener {
 
     @Override
     public void start() {

+ 2 - 2
dbsyncer-listener/src/main/java/org/dbsyncer/listener/postgresql/MessageDecoder.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/postgresql/MessageDecoder.java

@@ -1,6 +1,6 @@
-package org.dbsyncer.listener.postgresql;
+package org.dbsyncer.connector.postgresql;
 
-import org.dbsyncer.listener.event.RowChangedEvent;
+import org.dbsyncer.sdk.listener.event.RowChangedEvent;
 import org.dbsyncer.connector.ConnectorFactory;
 import org.dbsyncer.sdk.config.DatabaseConfig;
 import org.dbsyncer.sdk.connector.database.DatabaseConnectorInstance;

+ 15 - 0
dbsyncer-connector/src/main/java/org/dbsyncer/connector/postgresql/PostgreSQLConnector.java

@@ -1,12 +1,15 @@
 package org.dbsyncer.connector.postgresql;
 
 import org.dbsyncer.common.util.StringUtil;
+import org.dbsyncer.connector.quartz.DatabaseQuartzListener;
 import org.dbsyncer.sdk.config.CommandConfig;
 import org.dbsyncer.sdk.config.DatabaseConfig;
 import org.dbsyncer.sdk.config.ReaderConfig;
 import org.dbsyncer.sdk.connector.database.AbstractDatabaseConnector;
 import org.dbsyncer.sdk.constant.DatabaseConstant;
+import org.dbsyncer.sdk.enums.ListenerTypeEnum;
 import org.dbsyncer.sdk.enums.TableTypeEnum;
+import org.dbsyncer.sdk.listener.Listener;
 import org.dbsyncer.sdk.model.PageSql;
 import org.dbsyncer.sdk.model.Table;
 import org.dbsyncer.sdk.util.PrimaryKeyUtil;
@@ -30,6 +33,18 @@ public final class PostgreSQLConnector extends AbstractDatabaseConnector {
         return TYPE;
     }
 
+    @Override
+    public Listener getListener(String listenerType) {
+        if (ListenerTypeEnum.isTiming(listenerType)) {
+            return new DatabaseQuartzListener();
+        }
+
+        if (ListenerTypeEnum.isLog(listenerType)) {
+            return new PostgreSQLListener();
+        }
+        return null;
+    }
+
     @PostConstruct
     private void init() {
         VALUE_MAPPERS.put(Types.BIT, new PostgreSQLBitValueMapper());

+ 14 - 14
dbsyncer-listener/src/main/java/org/dbsyncer/listener/postgresql/PostgreSQLExtractor.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/postgresql/PostgreSQLListener.java

@@ -1,14 +1,14 @@
-package org.dbsyncer.listener.postgresql;
+package org.dbsyncer.connector.postgresql;
 
-import org.dbsyncer.listener.model.ChangedOffset;
-import org.dbsyncer.listener.event.RowChangedEvent;
 import org.dbsyncer.common.util.BooleanUtil;
+import org.dbsyncer.connector.AbstractDatabaseListener;
+import org.dbsyncer.connector.ConnectorException;
+import org.dbsyncer.connector.postgresql.enums.MessageDecoderEnum;
 import org.dbsyncer.sdk.config.DatabaseConfig;
 import org.dbsyncer.sdk.connector.database.DatabaseConnectorInstance;
+import org.dbsyncer.sdk.listener.event.RowChangedEvent;
+import org.dbsyncer.sdk.model.ChangedOffset;
 import org.dbsyncer.sdk.util.DatabaseUtil;
-import org.dbsyncer.listener.AbstractDatabaseExtractor;
-import org.dbsyncer.listener.ListenerException;
-import org.dbsyncer.listener.postgresql.enums.MessageDecoderEnum;
 import org.postgresql.PGConnection;
 import org.postgresql.PGProperty;
 import org.postgresql.replication.LogSequenceNumber;
@@ -36,7 +36,7 @@ import java.util.concurrent.locks.ReentrantLock;
  * @version 1.0.0
  * @date 2022/4/10 22:36
  */
-public class PostgreSQLExtractor extends AbstractDatabaseExtractor {
+public class PostgreSQLListener extends AbstractDatabaseListener {
 
     private final Logger logger = LoggerFactory.getLogger(getClass());
 
@@ -75,7 +75,7 @@ public class PostgreSQLExtractor extends AbstractDatabaseExtractor {
 
             final String walLevel = connectorInstance.execute(databaseTemplate -> databaseTemplate.queryForObject(GET_WAL_LEVEL, String.class));
             if (!DEFAULT_WAL_LEVEL.equals(walLevel)) {
-                throw new ListenerException(String.format("Postgres server wal_level property must be \"%s\" but is: %s", DEFAULT_WAL_LEVEL, walLevel));
+                throw new ConnectorException(String.format("Postgres server wal_level property must be \"%s\" but is: %s", DEFAULT_WAL_LEVEL, walLevel));
             }
 
             final boolean hasAuth = connectorInstance.execute(databaseTemplate -> {
@@ -88,7 +88,7 @@ public class PostgreSQLExtractor extends AbstractDatabaseExtractor {
                 return login && (replication || superuser || admin || repAdmin);
             });
             if (!hasAuth) {
-                throw new ListenerException(String.format("Postgres roles LOGIN and REPLICATION are not assigned to user: %s", config.getUsername()));
+                throw new ConnectorException(String.format("Postgres roles LOGIN and REPLICATION are not assigned to user: %s", config.getUsername()));
             }
 
             database = connectorInstance.execute(databaseTemplate -> databaseTemplate.queryForObject(GET_DATABASE, String.class));
@@ -109,7 +109,7 @@ public class PostgreSQLExtractor extends AbstractDatabaseExtractor {
             logger.error("启动失败:{}", e.getMessage());
             DatabaseUtil.close(stream);
             DatabaseUtil.close(connection);
-            throw new ListenerException(e);
+            throw new ConnectorException(e);
         } finally {
             connectLock.unlock();
         }
@@ -184,9 +184,9 @@ public class PostgreSQLExtractor extends AbstractDatabaseExtractor {
         }
 
         if (!snapshot.containsKey(LSN_POSITION)) {
-            LogSequenceNumber lsn = connectorInstance.execute(databaseTemplate -> LogSequenceNumber.valueOf(databaseTemplate.queryForObject(GET_RESTART_LSN, new Object[] {database, slotName, plugin}, String.class)));
+            LogSequenceNumber lsn = connectorInstance.execute(databaseTemplate -> LogSequenceNumber.valueOf(databaseTemplate.queryForObject(GET_RESTART_LSN, new Object[]{database, slotName, plugin}, String.class)));
             if (null == lsn || lsn.asLong() == 0) {
-                throw new ListenerException("No maximum LSN recorded in the database");
+                throw new ConnectorException("No maximum LSN recorded in the database");
             }
             snapshot.put(LSN_POSITION, lsn.asString());
             super.forceFlushEvent();
@@ -280,7 +280,7 @@ public class PostgreSQLExtractor extends AbstractDatabaseExtractor {
 
                     // process decoder
                     RowChangedEvent event = messageDecoder.processMessage(msg);
-                    if(event != null){
+                    if (event != null) {
                         event.setPosition(lsn.asString());
                         sendChangedEvent(event);
                     }
@@ -289,7 +289,7 @@ public class PostgreSQLExtractor extends AbstractDatabaseExtractor {
                     stream.setAppliedLSN(lsn);
                     stream.setFlushedLSN(lsn);
                     stream.forceUpdateStatus();
-                } catch (IllegalStateException | ListenerException e) {
+                } catch (IllegalStateException | ConnectorException e) {
                     logger.error(e.getMessage());
                 } catch (Exception e) {
                     logger.error(e.getMessage(), e);

+ 23 - 13
dbsyncer-listener/src/main/java/org/dbsyncer/listener/postgresql/column/PgColumnValue.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/postgresql/column/PgColumnValue.java

@@ -1,11 +1,17 @@
-package org.dbsyncer.listener.postgresql.column;
+package org.dbsyncer.connector.postgresql.column;
 
 import org.dbsyncer.common.column.AbstractColumnValue;
 import org.dbsyncer.common.util.DateFormatUtil;
 import org.dbsyncer.common.util.StringUtil;
-import org.dbsyncer.listener.ListenerException;
+import org.dbsyncer.connector.ConnectorException;
 import org.postgresql.PGStatement;
-import org.postgresql.geometric.*;
+import org.postgresql.geometric.PGbox;
+import org.postgresql.geometric.PGcircle;
+import org.postgresql.geometric.PGline;
+import org.postgresql.geometric.PGlseg;
+import org.postgresql.geometric.PGpath;
+import org.postgresql.geometric.PGpoint;
+import org.postgresql.geometric.PGpolygon;
 import org.postgresql.util.PGInterval;
 import org.postgresql.util.PGmoney;
 import org.slf4j.Logger;
@@ -16,7 +22,11 @@ import java.sql.Date;
 import java.sql.SQLException;
 import java.sql.Time;
 import java.sql.Timestamp;
-import java.time.*;
+import java.time.Instant;
+import java.time.LocalTime;
+import java.time.OffsetDateTime;
+import java.time.OffsetTime;
+import java.time.ZoneOffset;
 import java.util.concurrent.TimeUnit;
 
 public final class PgColumnValue extends AbstractColumnValue<String> {
@@ -116,7 +126,7 @@ public final class PgColumnValue extends AbstractColumnValue<String> {
             return new PGbox(asString());
         } catch (final SQLException e) {
             logger.error("Failed to parse point {}, {}", asString(), e);
-            throw new ListenerException(e);
+            throw new ConnectorException(e);
         }
     }
 
@@ -125,7 +135,7 @@ public final class PgColumnValue extends AbstractColumnValue<String> {
             return new PGcircle(asString());
         } catch (final SQLException e) {
             logger.error("Failed to parse circle {}, {}", asString(), e);
-            throw new ListenerException(e);
+            throw new ConnectorException(e);
         }
     }
 
@@ -134,7 +144,7 @@ public final class PgColumnValue extends AbstractColumnValue<String> {
             return new PGInterval(asString());
         } catch (final SQLException e) {
             logger.error("Failed to parse point {}, {}", asString(), e);
-            throw new ListenerException(e);
+            throw new ConnectorException(e);
         }
     }
 
@@ -143,7 +153,7 @@ public final class PgColumnValue extends AbstractColumnValue<String> {
             return new PGline(asString());
         } catch (final SQLException e) {
             logger.error("Failed to parse point {}, {}", asString(), e);
-            throw new ListenerException(e);
+            throw new ConnectorException(e);
         }
     }
 
@@ -152,7 +162,7 @@ public final class PgColumnValue extends AbstractColumnValue<String> {
             return new PGlseg(asString());
         } catch (final SQLException e) {
             logger.error("Failed to parse point {}, {}", asString(), e);
-            throw new ListenerException(e);
+            throw new ConnectorException(e);
         }
     }
 
@@ -166,7 +176,7 @@ public final class PgColumnValue extends AbstractColumnValue<String> {
             return new PGmoney(asString());
         } catch (final SQLException e) {
             logger.error("Failed to parse money {}, {}", asString(), e);
-            throw new ListenerException(e);
+            throw new ConnectorException(e);
         }
     }
 
@@ -175,7 +185,7 @@ public final class PgColumnValue extends AbstractColumnValue<String> {
             return new PGpath(asString());
         } catch (final SQLException e) {
             logger.error("Failed to parse point {}, {}", asString(), e);
-            throw new ListenerException(e);
+            throw new ConnectorException(e);
         }
     }
 
@@ -184,7 +194,7 @@ public final class PgColumnValue extends AbstractColumnValue<String> {
             return new PGpoint(asString());
         } catch (final SQLException e) {
             logger.error("Failed to parse point {}, {}", asString(), e);
-            throw new ListenerException(e);
+            throw new ConnectorException(e);
         }
     }
 
@@ -193,7 +203,7 @@ public final class PgColumnValue extends AbstractColumnValue<String> {
             return new PGpolygon(asString());
         } catch (final SQLException e) {
             logger.error("Failed to parse point {}, {}", asString(), e);
-            throw new ListenerException(e);
+            throw new ConnectorException(e);
         }
     }
 

+ 8 - 8
dbsyncer-listener/src/main/java/org/dbsyncer/listener/postgresql/decoder/PgOutputMessageDecoder.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/postgresql/decoder/PgOutputMessageDecoder.java

@@ -1,13 +1,13 @@
-package org.dbsyncer.listener.postgresql.decoder;
+package org.dbsyncer.connector.postgresql.decoder;
 
 import org.dbsyncer.common.util.CollectionUtils;
+import org.dbsyncer.connector.ConnectorException;
 import org.dbsyncer.connector.ConnectorFactory;
-import org.dbsyncer.listener.ListenerException;
-import org.dbsyncer.listener.event.RowChangedEvent;
-import org.dbsyncer.listener.postgresql.AbstractMessageDecoder;
-import org.dbsyncer.listener.postgresql.enums.MessageDecoderEnum;
-import org.dbsyncer.listener.postgresql.enums.MessageTypeEnum;
+import org.dbsyncer.connector.postgresql.AbstractMessageDecoder;
+import org.dbsyncer.connector.postgresql.enums.MessageDecoderEnum;
+import org.dbsyncer.connector.postgresql.enums.MessageTypeEnum;
 import org.dbsyncer.sdk.connector.database.DatabaseConnectorInstance;
+import org.dbsyncer.sdk.listener.event.RowChangedEvent;
 import org.dbsyncer.sdk.model.Field;
 import org.dbsyncer.sdk.model.MetaInfo;
 import org.postgresql.replication.fluent.logical.ChainedLogicalStreamBuilder;
@@ -112,7 +112,7 @@ public class PgOutputMessageDecoder extends AbstractMessageDecoder {
                 return true;
             });
         } catch (Exception e) {
-            throw new ListenerException(e.getCause());
+            throw new ConnectorException(e.getCause());
         }
     }
 
@@ -158,7 +158,7 @@ public class PgOutputMessageDecoder extends AbstractMessageDecoder {
             // The table schema has been changed, we should be get a new table schema from db.
             MetaInfo metaInfo = connectorFactory.getMetaInfo(connectorInstance, tableId.tableName);
             if (CollectionUtils.isEmpty(metaInfo.getColumn())) {
-                throw new ListenerException(String.format("The table column for '%s' is empty.", tableId.tableName));
+                throw new ConnectorException(String.format("The table column for '%s' is empty.", tableId.tableName));
             }
             tableId.fields = metaInfo.getColumn();
             return;

+ 5 - 5
dbsyncer-listener/src/main/java/org/dbsyncer/listener/postgresql/decoder/TestDecodingMessageDecoder.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/postgresql/decoder/TestDecodingMessageDecoder.java

@@ -1,11 +1,11 @@
-package org.dbsyncer.listener.postgresql.decoder;
+package org.dbsyncer.connector.postgresql.decoder;
 
 import org.dbsyncer.common.column.Lexer;
-import org.dbsyncer.listener.event.RowChangedEvent;
+import org.dbsyncer.connector.postgresql.AbstractMessageDecoder;
+import org.dbsyncer.connector.postgresql.enums.MessageDecoderEnum;
+import org.dbsyncer.connector.postgresql.enums.MessageTypeEnum;
 import org.dbsyncer.sdk.constant.ConnectorConstant;
-import org.dbsyncer.listener.postgresql.AbstractMessageDecoder;
-import org.dbsyncer.listener.postgresql.enums.MessageDecoderEnum;
-import org.dbsyncer.listener.postgresql.enums.MessageTypeEnum;
+import org.dbsyncer.sdk.listener.event.RowChangedEvent;
 import org.postgresql.replication.fluent.logical.ChainedLogicalStreamBuilder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;

+ 6 - 6
dbsyncer-listener/src/main/java/org/dbsyncer/listener/postgresql/enums/MessageDecoderEnum.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/postgresql/enums/MessageDecoderEnum.java

@@ -1,10 +1,10 @@
-package org.dbsyncer.listener.postgresql.enums;
+package org.dbsyncer.connector.postgresql.enums;
 
 import org.dbsyncer.common.util.StringUtil;
-import org.dbsyncer.listener.ListenerException;
-import org.dbsyncer.listener.postgresql.MessageDecoder;
-import org.dbsyncer.listener.postgresql.decoder.PgOutputMessageDecoder;
-import org.dbsyncer.listener.postgresql.decoder.TestDecodingMessageDecoder;
+import org.dbsyncer.connector.ConnectorException;
+import org.dbsyncer.connector.postgresql.MessageDecoder;
+import org.dbsyncer.connector.postgresql.decoder.PgOutputMessageDecoder;
+import org.dbsyncer.connector.postgresql.decoder.TestDecodingMessageDecoder;
 
 /**
  * @author AE86
@@ -31,7 +31,7 @@ public enum MessageDecoderEnum {
         this.clazz = clazz;
     }
 
-    public static MessageDecoder getMessageDecoder(String type) throws ListenerException, IllegalAccessException, InstantiationException {
+    public static MessageDecoder getMessageDecoder(String type) throws ConnectorException, IllegalAccessException, InstantiationException {
         for (MessageDecoderEnum e : MessageDecoderEnum.values()) {
             if (StringUtil.equals(type, e.getType())) {
                 return (MessageDecoder) e.getClazz().newInstance();

+ 41 - 41
dbsyncer-listener/src/main/java/org/dbsyncer/listener/postgresql/enums/MessageTypeEnum.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/postgresql/enums/MessageTypeEnum.java

@@ -1,42 +1,42 @@
-package org.dbsyncer.listener.postgresql.enums;
-
-public enum MessageTypeEnum {
-    BEGIN,
-    COMMIT,
-    TABLE,
-    INSERT,
-    UPDATE,
-    DELETE,
-    RELATION,
-    TRUNCATE,
-    TYPE,
-    ORIGIN,
-    NONE;
-
-    public static MessageTypeEnum getType(char type) {
-        switch (type) {
-            case 'B':
-                return BEGIN;
-            case 'C':
-                return COMMIT;
-            case 't':
-                return TABLE;
-            case 'I':
-                return INSERT;
-            case 'U':
-                return UPDATE;
-            case 'D':
-                return DELETE;
-            case 'R':
-                return RELATION;
-            case 'Y':
-                return TYPE;
-            case 'O':
-                return ORIGIN;
-            case 'T':
-                return TRUNCATE;
-            default:
-                return NONE;
-        }
-    }
+package org.dbsyncer.connector.postgresql.enums;
+
+public enum MessageTypeEnum {
+    BEGIN,
+    COMMIT,
+    TABLE,
+    INSERT,
+    UPDATE,
+    DELETE,
+    RELATION,
+    TRUNCATE,
+    TYPE,
+    ORIGIN,
+    NONE;
+
+    public static MessageTypeEnum getType(char type) {
+        switch (type) {
+            case 'B':
+                return BEGIN;
+            case 'C':
+                return COMMIT;
+            case 't':
+                return TABLE;
+            case 'I':
+                return INSERT;
+            case 'U':
+                return UPDATE;
+            case 'D':
+                return DELETE;
+            case 'R':
+                return RELATION;
+            case 'Y':
+                return TYPE;
+            case 'O':
+                return ORIGIN;
+            case 'T':
+                return TRUNCATE;
+            default:
+                return NONE;
+        }
+    }
 }

+ 4 - 4
dbsyncer-listener/src/main/java/org/dbsyncer/listener/quartz/AbstractQuartzExtractor.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/quartz/AbstractQuartzListener.java

@@ -1,15 +1,15 @@
-package org.dbsyncer.listener.quartz;
+package org.dbsyncer.connector.quartz;
 
 import org.dbsyncer.common.model.Result;
 import org.dbsyncer.common.util.CollectionUtils;
 import org.dbsyncer.common.util.StringUtil;
 import org.dbsyncer.common.util.UUIDUtil;
+import org.dbsyncer.connector.AbstractListener;
 import org.dbsyncer.connector.scheduled.ScheduledTaskJob;
-import org.dbsyncer.listener.AbstractExtractor;
-import org.dbsyncer.listener.event.ScanChangedEvent;
 import org.dbsyncer.sdk.config.ReaderConfig;
 import org.dbsyncer.sdk.connector.ConnectorInstance;
 import org.dbsyncer.sdk.constant.ConnectorConstant;
+import org.dbsyncer.sdk.listener.event.ScanChangedEvent;
 import org.dbsyncer.sdk.model.Table;
 import org.dbsyncer.sdk.util.PrimaryKeyUtil;
 import org.slf4j.Logger;
@@ -30,7 +30,7 @@ import java.util.stream.Stream;
  * @Author AE86
  * @Date 2020-05-12 20:35
  */
-public abstract class AbstractQuartzExtractor extends AbstractExtractor implements ScheduledTaskJob {
+public abstract class AbstractQuartzListener extends AbstractListener implements ScheduledTaskJob {
 
     private final Logger logger = LoggerFactory.getLogger(getClass());
     private final String CURSOR = "cursor";

+ 3 - 3
dbsyncer-listener/src/main/java/org/dbsyncer/listener/quartz/DatabaseQuartzExtractor.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/quartz/DatabaseQuartzListener.java

@@ -1,9 +1,9 @@
-package org.dbsyncer.listener.quartz;
+package org.dbsyncer.connector.quartz;
 
 import org.dbsyncer.common.util.CollectionUtils;
 import org.dbsyncer.common.util.StringUtil;
+import org.dbsyncer.connector.enums.QuartzFilterEnum;
 import org.dbsyncer.sdk.constant.ConnectorConstant;
-import org.dbsyncer.listener.enums.QuartzFilterEnum;
 import org.springframework.util.Assert;
 
 import java.util.ArrayList;
@@ -22,7 +22,7 @@ import java.util.stream.Stream;
  * @Author AE86
  * @Date 2021-09-01 20:35
  */
-public final class DatabaseQuartzExtractor extends AbstractQuartzExtractor {
+public final class DatabaseQuartzListener extends AbstractQuartzListener {
 
     @Override
     protected Point checkLastPoint(Map<String, String> command, int index) {

+ 1 - 1
dbsyncer-listener/src/main/java/org/dbsyncer/listener/quartz/Point.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/quartz/Point.java

@@ -1,4 +1,4 @@
-package org.dbsyncer.listener.quartz;
+package org.dbsyncer.connector.quartz;
 
 import org.dbsyncer.common.util.StringUtil;
 

+ 1 - 1
dbsyncer-listener/src/main/java/org/dbsyncer/listener/quartz/QuartzFilter.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/quartz/QuartzFilter.java

@@ -1,4 +1,4 @@
-package org.dbsyncer.listener.quartz;
+package org.dbsyncer.connector.quartz;
 
 public interface QuartzFilter<T> {
 

+ 1 - 1
dbsyncer-listener/src/main/java/org/dbsyncer/listener/quartz/TableGroupQuartzCommand.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/quartz/TableGroupQuartzCommand.java

@@ -1,4 +1,4 @@
-package org.dbsyncer.listener.quartz;
+package org.dbsyncer.connector.quartz;
 
 import org.dbsyncer.sdk.model.Table;
 import org.dbsyncer.sdk.util.PrimaryKeyUtil;

+ 2 - 2
dbsyncer-listener/src/main/java/org/dbsyncer/listener/quartz/filter/DateFilter.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/quartz/filter/DateFilter.java

@@ -1,7 +1,7 @@
-package org.dbsyncer.listener.quartz.filter;
+package org.dbsyncer.connector.quartz.filter;
 
 import org.dbsyncer.common.util.DateFormatUtil;
-import org.dbsyncer.listener.quartz.QuartzFilter;
+import org.dbsyncer.connector.quartz.QuartzFilter;
 
 import java.sql.Date;
 import java.time.Instant;

+ 2 - 2
dbsyncer-listener/src/main/java/org/dbsyncer/listener/quartz/filter/TimestampFilter.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/quartz/filter/TimestampFilter.java

@@ -1,6 +1,6 @@
-package org.dbsyncer.listener.quartz.filter;
+package org.dbsyncer.connector.quartz.filter;
 
-import org.dbsyncer.listener.quartz.QuartzFilter;
+import org.dbsyncer.connector.quartz.QuartzFilter;
 
 import java.sql.Timestamp;
 import java.time.Instant;

+ 2 - 2
dbsyncer-listener/src/main/java/org/dbsyncer/listener/quartz/filter/YesDateFilter.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/quartz/filter/YesDateFilter.java

@@ -1,7 +1,7 @@
-package org.dbsyncer.listener.quartz.filter;
+package org.dbsyncer.connector.quartz.filter;
 
 import org.dbsyncer.common.util.DateFormatUtil;
-import org.dbsyncer.listener.quartz.QuartzFilter;
+import org.dbsyncer.connector.quartz.QuartzFilter;
 
 import java.sql.Date;
 import java.time.LocalDateTime;

+ 2 - 2
dbsyncer-listener/src/main/java/org/dbsyncer/listener/quartz/filter/YesTimestampFilter.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/quartz/filter/YesTimestampFilter.java

@@ -1,6 +1,6 @@
-package org.dbsyncer.listener.quartz.filter;
+package org.dbsyncer.connector.quartz.filter;
 
-import org.dbsyncer.listener.quartz.QuartzFilter;
+import org.dbsyncer.connector.quartz.QuartzFilter;
 
 import java.sql.Timestamp;
 import java.time.LocalDateTime;

+ 16 - 0
dbsyncer-connector/src/main/java/org/dbsyncer/connector/sql/DQLMySQLConnector.java

@@ -1,9 +1,13 @@
 package org.dbsyncer.connector.sql;
 
+import org.dbsyncer.connector.mysql.DqlMySQLListener;
+import org.dbsyncer.connector.quartz.DatabaseQuartzListener;
 import org.dbsyncer.sdk.config.CommandConfig;
 import org.dbsyncer.sdk.config.ReaderConfig;
 import org.dbsyncer.sdk.connector.database.AbstractDQLConnector;
 import org.dbsyncer.sdk.constant.DatabaseConstant;
+import org.dbsyncer.sdk.enums.ListenerTypeEnum;
+import org.dbsyncer.sdk.listener.Listener;
 import org.dbsyncer.sdk.model.PageSql;
 import org.springframework.stereotype.Component;
 
@@ -35,4 +39,16 @@ public final class DQLMySQLConnector extends AbstractDQLConnector {
     public Map<String, String> getSourceCommand(CommandConfig commandConfig) {
         return super.getDqlSourceCommand(commandConfig, true);
     }
+
+    @Override
+    public Listener getListener(String listenerType) {
+        if (ListenerTypeEnum.isTiming(listenerType)) {
+            return new DatabaseQuartzListener();
+        }
+
+        if (ListenerTypeEnum.isLog(listenerType)) {
+            return new DqlMySQLListener();
+        }
+        return null;
+    }
 }

+ 16 - 0
dbsyncer-connector/src/main/java/org/dbsyncer/connector/sql/DQLOracleConnector.java

@@ -1,8 +1,12 @@
 package org.dbsyncer.connector.sql;
 
+import org.dbsyncer.connector.oracle.DqlOracleListener;
+import org.dbsyncer.connector.quartz.DatabaseQuartzListener;
 import org.dbsyncer.sdk.config.ReaderConfig;
 import org.dbsyncer.sdk.connector.database.AbstractDQLConnector;
 import org.dbsyncer.sdk.constant.DatabaseConstant;
+import org.dbsyncer.sdk.enums.ListenerTypeEnum;
+import org.dbsyncer.sdk.listener.Listener;
 import org.dbsyncer.sdk.model.PageSql;
 import org.springframework.stereotype.Component;
 
@@ -16,6 +20,18 @@ public final class DQLOracleConnector extends AbstractDQLConnector {
         return TYPE;
     }
 
+    @Override
+    public Listener getListener(String listenerType) {
+        if (ListenerTypeEnum.isTiming(listenerType)) {
+            return new DatabaseQuartzListener();
+        }
+
+        if (ListenerTypeEnum.isLog(listenerType)) {
+            return new DqlOracleListener();
+        }
+        return null;
+    }
+
     @Override
     public String getPageSql(PageSql config) {
         return DatabaseConstant.ORACLE_PAGE_SQL_START + config.getQuerySql() + DatabaseConstant.ORACLE_PAGE_SQL_END;

+ 16 - 0
dbsyncer-connector/src/main/java/org/dbsyncer/connector/sql/DQLPostgreSQLConnector.java

@@ -1,8 +1,12 @@
 package org.dbsyncer.connector.sql;
 
+import org.dbsyncer.connector.postgresql.DqlPostgreSQLListener;
+import org.dbsyncer.connector.quartz.DatabaseQuartzListener;
 import org.dbsyncer.sdk.config.ReaderConfig;
 import org.dbsyncer.sdk.connector.database.AbstractDQLConnector;
 import org.dbsyncer.sdk.constant.DatabaseConstant;
+import org.dbsyncer.sdk.enums.ListenerTypeEnum;
+import org.dbsyncer.sdk.listener.Listener;
 import org.dbsyncer.sdk.model.PageSql;
 import org.springframework.stereotype.Component;
 
@@ -16,6 +20,18 @@ public final class DQLPostgreSQLConnector extends AbstractDQLConnector {
         return TYPE;
     }
 
+    @Override
+    public Listener getListener(String listenerType) {
+        if (ListenerTypeEnum.isTiming(listenerType)) {
+            return new DatabaseQuartzListener();
+        }
+
+        if (ListenerTypeEnum.isLog(listenerType)) {
+            return new DqlPostgreSQLListener();
+        }
+        return null;
+    }
+
     @Override
     public String getPageSql(PageSql config) {
         return config.getQuerySql() + DatabaseConstant.POSTGRESQL_PAGE_SQL;

+ 16 - 0
dbsyncer-connector/src/main/java/org/dbsyncer/connector/sql/DQLSqlServerConnector.java

@@ -1,9 +1,13 @@
 package org.dbsyncer.connector.sql;
 
 import org.dbsyncer.common.util.StringUtil;
+import org.dbsyncer.connector.quartz.DatabaseQuartzListener;
+import org.dbsyncer.connector.sqlserver.DqlSqlServerListener;
 import org.dbsyncer.sdk.config.ReaderConfig;
 import org.dbsyncer.sdk.connector.database.AbstractDQLConnector;
 import org.dbsyncer.sdk.constant.DatabaseConstant;
+import org.dbsyncer.sdk.enums.ListenerTypeEnum;
+import org.dbsyncer.sdk.listener.Listener;
 import org.dbsyncer.sdk.model.PageSql;
 import org.springframework.stereotype.Component;
 
@@ -19,6 +23,18 @@ public final class DQLSqlServerConnector extends AbstractDQLConnector {
         return TYPE;
     }
 
+    @Override
+    public Listener getListener(String listenerType) {
+        if (ListenerTypeEnum.isTiming(listenerType)) {
+            return new DatabaseQuartzListener();
+        }
+
+        if (ListenerTypeEnum.isLog(listenerType)) {
+            return new DqlSqlServerListener();
+        }
+        return null;
+    }
+
     @Override
     public String getPageSql(PageSql config) {
         List<String> primaryKeys = config.getPrimaryKeys();

+ 1 - 1
dbsyncer-listener/src/main/java/org/dbsyncer/listener/sqlserver/CDCEvent.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/sqlserver/CDCEvent.java

@@ -1,7 +1,7 @@
 /**
  * DBSyncer Copyright 2019-2024 All Rights Reserved.
  */
-package org.dbsyncer.listener.sqlserver;
+package org.dbsyncer.connector.sqlserver;
 
 import java.util.List;
 

+ 3 - 3
dbsyncer-listener/src/main/java/org/dbsyncer/listener/sqlserver/DqlSqlServerExtractor.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/sqlserver/DqlSqlServerListener.java

@@ -1,13 +1,13 @@
-package org.dbsyncer.listener.sqlserver;
+package org.dbsyncer.connector.sqlserver;
 
-import org.dbsyncer.listener.ChangedEvent;
+import org.dbsyncer.sdk.listener.ChangedEvent;
 
 /**
  * @author AE86
  * @version 1.0.0
  * @date 2022/5/22 22:56
  */
-public class DqlSqlServerExtractor extends SqlServerExtractor {
+public class DqlSqlServerListener extends SqlServerListener {
 
     @Override
     public void start() {

+ 1 - 1
dbsyncer-listener/src/main/java/org/dbsyncer/listener/sqlserver/Lsn.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/sqlserver/Lsn.java

@@ -1,4 +1,4 @@
-package org.dbsyncer.listener.sqlserver;
+package org.dbsyncer.connector.sqlserver;
 
 import java.util.Arrays;
 

+ 8 - 8
dbsyncer-listener/src/main/java/org/dbsyncer/listener/sqlserver/LsnPuller.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/sqlserver/LsnPuller.java

@@ -1,4 +1,4 @@
-package org.dbsyncer.listener.sqlserver;
+package org.dbsyncer.connector.sqlserver;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -21,7 +21,7 @@ public class LsnPuller {
      */
     private static final long DEFAULT_POLL_INTERVAL_MILLIS = 100;
     private static volatile LsnPuller instance = null;
-    private final Map<String, SqlServerExtractor> map = new ConcurrentHashMap<>();
+    private final Map<String, SqlServerListener> map = new ConcurrentHashMap<>();
     private Worker worker;
 
     private LsnPuller() {
@@ -46,8 +46,8 @@ public class LsnPuller {
         worker.start();
     }
 
-    public static void addExtractor(String metaId, SqlServerExtractor extractor) {
-        getInstance().map.put(metaId, extractor);
+    public static void addExtractor(String metaId, SqlServerListener listener) {
+        getInstance().map.put(metaId, listener);
     }
 
     public static void removeExtractor(String metaId) {
@@ -65,10 +65,10 @@ public class LsnPuller {
                         continue;
                     }
                     Lsn maxLsn = null;
-                    for (SqlServerExtractor extractor : map.values()) {
-                        maxLsn = extractor.getMaxLsn();
-                        if (null != maxLsn && maxLsn.isAvailable() && maxLsn.compareTo(extractor.getLastLsn()) > 0) {
-                            extractor.pushStopLsn(maxLsn);
+                    for (SqlServerListener listener : map.values()) {
+                        maxLsn = listener.getMaxLsn();
+                        if (null != maxLsn && maxLsn.isAvailable() && maxLsn.compareTo(listener.getLastLsn()) > 0) {
+                            listener.pushStopLsn(maxLsn);
                         }
                     }
                     TimeUnit.MILLISECONDS.sleep(DEFAULT_POLL_INTERVAL_MILLIS);

+ 2 - 4
dbsyncer-listener/src/main/java/org/dbsyncer/listener/sqlserver/SqlServerChangeTable.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/sqlserver/SqlServerChangeTable.java

@@ -1,4 +1,4 @@
-package org.dbsyncer.listener.sqlserver;
+package org.dbsyncer.connector.sqlserver;
 
 import org.dbsyncer.common.util.JsonUtil;
 
@@ -12,9 +12,7 @@ public class SqlServerChangeTable {
     private byte[] stopLsn;
     private String capturedColumns;
 
-    public SqlServerChangeTable(String schemaName, String tableName, String captureInstance,
-                                int changeTableObjectId,
-                                byte[] startLsn, byte[] stopLsn, String capturedColumns) {
+    public SqlServerChangeTable(String schemaName, String tableName, String captureInstance, int changeTableObjectId, byte[] startLsn, byte[] stopLsn, String capturedColumns) {
         this.schemaName = schemaName;
         this.tableName = tableName;
         this.captureInstance = captureInstance;

+ 19 - 20
dbsyncer-connector/src/main/java/org/dbsyncer/connector/sqlserver/SqlServerConnector.java

@@ -2,22 +2,23 @@ package org.dbsyncer.connector.sqlserver;
 
 import org.dbsyncer.common.util.CollectionUtils;
 import org.dbsyncer.common.util.StringUtil;
-import org.dbsyncer.sdk.config.DatabaseConfig;
+import org.dbsyncer.connector.quartz.DatabaseQuartzListener;
 import org.dbsyncer.sdk.config.CommandConfig;
+import org.dbsyncer.sdk.config.DatabaseConfig;
 import org.dbsyncer.sdk.config.ReaderConfig;
-import org.dbsyncer.sdk.constant.DatabaseConstant;
 import org.dbsyncer.sdk.connector.database.AbstractDatabaseConnector;
 import org.dbsyncer.sdk.connector.database.DatabaseConnectorInstance;
+import org.dbsyncer.sdk.constant.DatabaseConstant;
+import org.dbsyncer.sdk.enums.ListenerTypeEnum;
 import org.dbsyncer.sdk.enums.TableTypeEnum;
-import org.dbsyncer.sdk.model.PageSql;
+import org.dbsyncer.sdk.listener.Listener;
 import org.dbsyncer.sdk.model.Field;
+import org.dbsyncer.sdk.model.PageSql;
 import org.dbsyncer.sdk.model.Table;
 import org.springframework.stereotype.Component;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
 @Component
@@ -42,6 +43,18 @@ public final class SqlServerConnector extends AbstractDatabaseConnector {
         return tables;
     }
 
+    @Override
+    public Listener getListener(String listenerType) {
+        if (ListenerTypeEnum.isTiming(listenerType)) {
+            return new DatabaseQuartzListener();
+        }
+
+        if (ListenerTypeEnum.isLog(listenerType)) {
+            return new SqlServerListener();
+        }
+        return null;
+    }
+
     @Override
     public String getPageSql(PageSql config) {
         List<String> primaryKeys = buildPrimaryKeys(config.getPrimaryKeys());
@@ -53,7 +66,7 @@ public final class SqlServerConnector extends AbstractDatabaseConnector {
     public Object[] getPageArgs(ReaderConfig config) {
         int pageSize = config.getPageSize();
         int pageIndex = config.getPageIndex();
-        return new Object[] {(pageIndex - 1) * pageSize + 1, pageIndex * pageSize};
+        return new Object[]{(pageIndex - 1) * pageSize + 1, pageIndex * pageSize};
     }
 
     @Override
@@ -100,18 +113,4 @@ public final class SqlServerConnector extends AbstractDatabaseConnector {
         return new StringBuilder("[").append(key).append("]").toString();
     }
 
-    /**
-     * 是否包含系统关键字
-     *
-     * @param regex
-     * @param val
-     * @return
-     */
-    private boolean containsKeyword(String regex, String val) {
-        if (StringUtil.isNotBlank(val)) {
-            Matcher matcher = Pattern.compile(regex).matcher(val.toLowerCase());
-            return matcher.find();
-        }
-        return false;
-    }
 }

+ 9 - 9
dbsyncer-listener/src/main/java/org/dbsyncer/listener/sqlserver/SqlServerExtractor.java → dbsyncer-connector/src/main/java/org/dbsyncer/connector/sqlserver/SqlServerListener.java

@@ -1,15 +1,15 @@
-package org.dbsyncer.listener.sqlserver;
+package org.dbsyncer.connector.sqlserver;
 
 import com.microsoft.sqlserver.jdbc.SQLServerException;
-import org.dbsyncer.listener.model.ChangedOffset;
-import org.dbsyncer.listener.event.RowChangedEvent;
+import org.dbsyncer.connector.ConnectorException;
+import org.dbsyncer.connector.enums.TableOperationEnum;
+import org.dbsyncer.sdk.model.ChangedOffset;
+import org.dbsyncer.sdk.listener.event.RowChangedEvent;
 import org.dbsyncer.common.util.CollectionUtils;
 import org.dbsyncer.sdk.config.DatabaseConfig;
 import org.dbsyncer.sdk.constant.ConnectorConstant;
 import org.dbsyncer.sdk.connector.database.DatabaseConnectorInstance;
-import org.dbsyncer.listener.AbstractDatabaseExtractor;
-import org.dbsyncer.listener.ListenerException;
-import org.dbsyncer.listener.enums.TableOperationEnum;
+import org.dbsyncer.connector.AbstractDatabaseListener;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.util.Assert;
@@ -36,7 +36,7 @@ import java.util.concurrent.locks.ReentrantLock;
  * @Author AE86
  * @Date 2021-06-18 01:20
  */
-public class SqlServerExtractor extends AbstractDatabaseExtractor {
+public class SqlServerListener extends AbstractDatabaseListener {
 
     private final Logger logger = LoggerFactory.getLogger(getClass());
 
@@ -99,7 +99,7 @@ public class SqlServerExtractor extends AbstractDatabaseExtractor {
         } catch (Exception e) {
             close();
             logger.error("启动失败:{}", e.getMessage());
-            throw new ListenerException(e);
+            throw new ConnectorException(e);
         } finally {
             connectLock.unlock();
         }
@@ -152,7 +152,7 @@ public class SqlServerExtractor extends AbstractDatabaseExtractor {
                 return;
             }
             // Shouldn't happen if the agent is running, but it is better to guard against such situation
-            throw new ListenerException("No maximum LSN recorded in the database");
+            throw new ConnectorException("No maximum LSN recorded in the database");
         }
         lastLsn = Lsn.valueOf(snapshot.get(LSN_POSITION));
     }

+ 2 - 2
dbsyncer-listener/src/main/test/BinaryLogRemoteClientTest.java → dbsyncer-connector/src/main/test/BinaryLogRemoteClientTest.java

@@ -1,7 +1,7 @@
 import com.github.shyiko.mysql.binlog.event.*;
 import org.dbsyncer.sdk.constant.ConnectorConstant;
-import org.dbsyncer.listener.mysql.BinaryLogClient;
-import org.dbsyncer.listener.mysql.BinaryLogRemoteClient;
+import org.dbsyncer.connector.mysql.BinaryLogClient;
+import org.dbsyncer.connector.mysql.BinaryLogRemoteClient;
 import org.junit.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;

+ 314 - 314
dbsyncer-listener/src/main/test/ChangeDataCaptureTest.java → dbsyncer-connector/src/main/test/ChangeDataCaptureTest.java

@@ -1,315 +1,315 @@
-import com.microsoft.sqlserver.jdbc.SQLServerException;
-import org.dbsyncer.common.util.CollectionUtils;
-import org.dbsyncer.listener.sqlserver.Lsn;
-import org.dbsyncer.listener.sqlserver.SqlServerChangeTable;
-import org.junit.Assert;
-import org.junit.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.sql.*;
-import java.util.*;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.regex.Matcher;
-
-/**
- * @version 1.0.0
- * @Author AE86
- * @Date 2021-06-14 01:55
- * @Link https://www.red-gate.com/simple-talk/sql/learn-sql-server/introduction-to-change-data-capture-cdc-in-sql-server-2008/
- */
-public class ChangeDataCaptureTest {
-
-    private final Logger logger = LoggerFactory.getLogger(getClass());
-
-    private static final String STATEMENTS_PLACEHOLDER = "#";
-    private static final String GET_DATABASE_NAME = "SELECT db_name()";
-    private static final String GET_DATABASE_VERSION = "SELECT @@VERSION AS 'SQL Server Version'";
-    private static final String GET_TABLE_LIST = "SELECT NAME FROM SYS.TABLES WHERE SCHEMA_ID = SCHEMA_ID('#') AND IS_MS_SHIPPED = 0";
-    private static final String IS_SERVER_AGENT_RUNNING = "EXEC master.#.xp_servicecontrol N'QUERYSTATE', N'SQLSERVERAGENT'";
-    private static final String IS_DB_CDC_ENABLED = "SELECT is_cdc_enabled FROM sys.databases WHERE name = '#'";
-    private static final String IS_TABLE_CDC_ENABLED = "SELECT COUNT(*) FROM sys.tables tb WHERE tb.is_tracked_by_cdc = 1 AND tb.name='#'";
-    private static final String ENABLE_DB_CDC = "IF EXISTS(select 1 from sys.databases where name = '#' AND is_cdc_enabled=0) EXEC sys.sp_cdc_enable_db";
-    private static final String ENABLE_TABLE_CDC = "IF EXISTS(select 1 from sys.tables where name = '#' AND is_tracked_by_cdc=0) EXEC sys.sp_cdc_enable_table @source_schema = N'%s', @source_name = N'#', @role_name = NULL, @supports_net_changes = 0";
-    private static final String DISABLE_TABLE_CDC = "EXEC sys.sp_cdc_disable_table @source_schema = N'%s', @source_name = N'#', @capture_instance = 'all'";
-
-    private static final String AT_TIME_ZONE_UTC = " AT TIME ZONE 'UTC'";
-    private static final String GET_ALL_CHANGES_FOR_TABLE = "SELECT sys.fn_cdc_map_lsn_to_time([__$start_lsn])#, * FROM cdc.[fn_cdc_get_all_changes_#](?, ?, N'all update old') order by [__$start_lsn] ASC, [__$seqval] ASC, [__$operation] ASC";
-    private static final String GET_TABLES_CDC_ENABLED = "EXEC sys.sp_cdc_help_change_data_capture";
-    private static final String GET_MAX_LSN = "SELECT sys.fn_cdc_get_max_lsn()";
-    private static final String GET_MIN_LSN = "SELECT sys.fn_cdc_get_min_lsn('#')";
-    private static final String GET_INCREMENT_LSN = "SELECT sys.fn_cdc_increment_lsn(?)";
-
-    private String realDatabaseName;
-    private String getAllChangesForTable;
-    private Connection connection = null;
-    private Set<String> tables;
-    private String schema;
-
-    /**
-     * <p>cdc.captured_columns – 此表返回捕获列列表的结果。</p>
-     * <p>cdc.change_tables – 此表返回所有启用捕获的表的列表。</p>
-     * <p>cdc.ddl_history – 此表包含自启用捕获数据以来所有 DDL 更改的历史记录。</p>
-     * <p>cdc.index_columns – 该表包含与变更表相关的索引。</p>
-     * <p>cdc.lsn_time_mapping – 此表映射 LSN编号(唯一序列号标识, 增加数字) 和时间。</p>
-     * <p>cdc.fn_cdc_get_all_changes_MY_USER - 可用于获取在特定时间段内发生的事件</p>
-     * <p>sys.fn_cdc_map_time_to_lsn - 表中是否有 tran_end_time值大于或等于指定时间的行。例如,可以用此查询来确定捕获进程是否已处理完截至前指定时间提交的更改</p>
-     * <p>sys.fn_cdc_get_max_lsn</p>
-     * <p>sys.sp_cdc_cleanup_change_table 默认情况下间隔为3天清理日志数据</p>
-     *
-     * @throws SQLException
-     */
-    @Test
-    public void testConnect() throws SQLException, InterruptedException {
-        ChangeDataCaptureTest cdc = new ChangeDataCaptureTest();
-        cdc.start();
-
-        // 获取数据库名 test
-        realDatabaseName = cdc.queryAndMap(GET_DATABASE_NAME, rs -> rs.getString(1));
-        logger.info("数据库名:{}", realDatabaseName);
-        // As per https://www.mssqltips.com/sqlservertip/1140/how-to-tell-what-sql-server-version-you-are-running/
-        // Always beginning with 'Microsoft SQL Server NNNN' but only in case SQL Server is standalone
-        String version = cdc.queryAndMap(GET_DATABASE_VERSION, rs -> rs.getString(1));
-        boolean supportsAtTimeZone = false;
-        if (version.startsWith("Microsoft SQL Server ")) {
-            supportsAtTimeZone = 2016 < Integer.valueOf(version.substring(21, 25));
-        }
-        logger.info("数据库版本:{}", version);
-        tables = cdc.queryAndMapList(GET_TABLE_LIST.replace(STATEMENTS_PLACEHOLDER, realDatabaseName), rs -> {
-            Set<String> table = new LinkedHashSet<>();
-            while (rs.next()) {
-                table.add(rs.getString(1));
-            }
-            return table;
-        });
-        logger.info("所有表:{}", tables);
-        // 获取Agent服务状态 Stopped. Running.
-        boolean enabledServerAgent = cdc.queryAndMap(IS_SERVER_AGENT_RUNNING.replace(STATEMENTS_PLACEHOLDER, realDatabaseName), rs -> "Running.".equals(rs.getString(1)));
-        logger.info("是否启动Agent服务:{}", enabledServerAgent);
-        Assert.assertTrue("The agent server is not running", enabledServerAgent);
-        boolean enabledCDC = cdc.queryAndMap(IS_DB_CDC_ENABLED.replace(STATEMENTS_PLACEHOLDER, realDatabaseName), rs -> rs.getBoolean(1));
-        logger.info("是否启用CDC库[{}]:{}", realDatabaseName, enabledCDC);
-        if (!enabledCDC) {
-            cdc.execute(ENABLE_DB_CDC.replace(STATEMENTS_PLACEHOLDER, realDatabaseName));
-            // make sure DB has cdc-enabled before proceeding
-            TimeUnit.SECONDS.sleep(3);
-        }
-
-        // 注册CDC表
-        tables.forEach(table -> {
-            try {
-                boolean enabledTableCDC = cdc.queryAndMap(IS_TABLE_CDC_ENABLED.replace(STATEMENTS_PLACEHOLDER, table), rs -> rs.getInt(1) > 0);
-                logger.info("是否启用CDC表[{}]:{}", table, enabledTableCDC);
-                if (!enabledTableCDC) {
-                    cdc.execute(String.format(ENABLE_TABLE_CDC.replace(STATEMENTS_PLACEHOLDER, table), schema));
-                    Lsn minLsn = cdc.queryAndMap(GET_MIN_LSN.replace(STATEMENTS_PLACEHOLDER, table), rs -> new Lsn(rs.getBytes(1)));
-                    logger.info("启用CDC表[{}]:{}", table, minLsn.isAvailable());
-                }
-            } catch (SQLException e) {
-                logger.error(e.getMessage());
-            }
-        });
-
-        // 支持UTC
-        getAllChangesForTable = GET_ALL_CHANGES_FOR_TABLE.replaceFirst(STATEMENTS_PLACEHOLDER, Matcher.quoteReplacement(supportsAtTimeZone ? AT_TIME_ZONE_UTC : ""));
-
-        // 读取增量
-        Set<SqlServerChangeTable> changeTables = cdc.queryAndMapList(GET_TABLES_CDC_ENABLED, rs -> {
-            final Set<SqlServerChangeTable> tables = new HashSet<>();
-            while (rs.next()) {
-                SqlServerChangeTable changeTable = new SqlServerChangeTable(
-                        // schemaName
-                        rs.getString(1),
-                        // tableName
-                        rs.getString(2),
-                        // captureInstance
-                        rs.getString(3),
-                        // changeTableObjectId
-                        rs.getInt(4),
-                        // startLsn
-                        rs.getBytes(6),
-                        // stopLsn
-                        rs.getBytes(7),
-                        // capturedColumns
-                        rs.getString(15));
-                logger.info(changeTable.toString());
-                tables.add(changeTable);
-            }
-            return tables;
-        });
-        logger.info("监听表数:{} ", changeTables.size());
-
-        if (!CollectionUtils.isEmpty(changeTables)) {
-            AtomicInteger count = new AtomicInteger(0);
-            Lsn lastLsn = cdc.queryAndMap(GET_MAX_LSN, rs -> new Lsn(rs.getBytes(1)));
-
-            while (true && count.getAndAdd(1) < 30) {
-                Lsn stopLsn = cdc.queryAndMap(GET_MAX_LSN, rs -> new Lsn(rs.getBytes(1)));
-                if (!stopLsn.isAvailable()) {
-                    logger.warn("No maximum LSN recorded in the database; please ensure that the SQL Server Agent is running");
-                    cdc.pause();
-                    continue;
-                }
-
-                if (stopLsn.compareTo(lastLsn) <= 0) {
-                    cdc.pause();
-                    continue;
-                }
-
-                Lsn startLsn = getIncrementLsn(cdc, lastLsn);
-                changeTables.forEach(changeTable -> {
-                    try {
-                        final String query = getAllChangesForTable.replace(STATEMENTS_PLACEHOLDER, changeTable.getCaptureInstance());
-                        logger.info("Getting changes for table {} in range[{}, {}]", changeTable.getTableName(), startLsn, stopLsn);
-
-                        cdc.queryAndMapList(query, statement -> {
-                            statement.setBytes(1, startLsn.getBinary());
-                            statement.setBytes(2, stopLsn.getBinary());
-                        }, rs -> {
-                            int columnCount = rs.getMetaData().getColumnCount();
-                            List<List<Object>> data = new ArrayList<>(columnCount);
-                            List<Object> row = null;
-                            while (rs.next()) {
-                                row = new ArrayList<>(columnCount);
-                                for (int i = 1; i <= columnCount; i++) {
-                                    row.add(rs.getObject(i));
-                                }
-                                logger.info("rows:{}", row);
-                                data.add(row);
-                            }
-                            return data;
-                        });
-                    } catch (Exception e) {
-                        e.printStackTrace();
-                    }
-                });
-
-                lastLsn = stopLsn;
-            }
-        }
-
-        // 注销CDC表
-        for (String table : tables) {
-            cdc.execute(String.format(DISABLE_TABLE_CDC.replace(STATEMENTS_PLACEHOLDER, table), schema));
-        }
-        cdc.close();
-    }
-
-    public void start() throws SQLException {
-        String username = "sa";
-        String password = "123";
-        String url = "jdbc:sqlserver://127.0.0.1:1433;DatabaseName=test";
-        schema = "dbo";
-        connection = DriverManager.getConnection(url, username, password);
-        if (connection != null) {
-            DatabaseMetaData dm = (DatabaseMetaData) connection.getMetaData();
-            logger.info("Driver name: " + dm.getDriverName());
-            logger.info("Driver version: " + dm.getDriverVersion());
-            logger.info("Product name: " + dm.getDatabaseProductName());
-            logger.info("Product version: " + dm.getDatabaseProductVersion());
-        }
-    }
-
-    public void close() {
-        if (null != connection) {
-            close(connection);
-        }
-    }
-
-    private void close(AutoCloseable closeable) {
-        if (null != closeable) {
-            try {
-                closeable.close();
-            } catch (Exception e) {
-                logger.error(e.getMessage());
-            }
-        }
-    }
-
-    private Lsn getIncrementLsn(ChangeDataCaptureTest cdc, Lsn lastLsn) {
-        return cdc.queryAndMap(GET_INCREMENT_LSN, statement -> statement.setBytes(1, lastLsn.getBinary()), rs -> Lsn.valueOf(rs.getBytes(1)));
-    }
-
-    private void pause() throws InterruptedException {
-        TimeUnit.SECONDS.sleep(2);
-    }
-
-    private void execute(String... sqlStatements) throws SQLException {
-        Statement statement = connection.createStatement();
-        try {
-            for (String sqlStatement : sqlStatements) {
-                if (sqlStatement != null) {
-                    logger.info("executing '{}'", sqlStatement);
-                    statement.execute(sqlStatement);
-                }
-            }
-        } catch (Exception e) {
-            logger.error(e.getMessage());
-        } finally {
-            close(statement);
-        }
-    }
-
-    public interface ResultSetMapper<T> {
-        T apply(ResultSet rs) throws SQLException;
-    }
-
-    public interface StatementPreparer {
-        void accept(PreparedStatement statement) throws SQLException;
-    }
-
-    public <T> T queryAndMap(String sql, ResultSetMapper<T> mapper) {
-        return queryAndMap(sql, null, mapper);
-    }
-
-    public <T> T queryAndMap(String sql, StatementPreparer statementPreparer, ResultSetMapper<T> mapper) {
-        PreparedStatement ps = null;
-        ResultSet rs = null;
-        T apply = null;
-        try {
-            ps = connection.prepareStatement(sql);
-            if (null != statementPreparer) {
-                statementPreparer.accept(ps);
-            }
-            rs = ps.executeQuery();
-            if (rs.next()) {
-                apply = mapper.apply(rs);
-            }
-        } catch (Exception e) {
-            logger.error(e.getMessage());
-        } finally {
-            close(rs);
-            close(ps);
-        }
-        return apply;
-    }
-
-    public <T> T queryAndMapList(String sql, ResultSetMapper<T> mapper) {
-        return queryAndMapList(sql, null, mapper);
-    }
-
-    public <T> T queryAndMapList(String sql, StatementPreparer statementPreparer, ResultSetMapper<T> mapper) {
-        PreparedStatement ps = null;
-        ResultSet rs = null;
-        T apply = null;
-        try {
-            ps = connection.prepareStatement(sql);
-            if (null != statementPreparer) {
-                statementPreparer.accept(ps);
-            }
-            rs = ps.executeQuery();
-            apply = mapper.apply(rs);
-        } catch (SQLServerException e) {
-            // 为过程或函数 cdc.fn_cdc_get_all_changes_ ...  提供的参数数目不足。
-            logger.warn(e.getMessage());
-        } catch (Exception e) {
-            logger.error(e.getMessage());
-        } finally {
-            close(rs);
-            close(ps);
-        }
-        return apply;
-    }
-
+import com.microsoft.sqlserver.jdbc.SQLServerException;
+import org.dbsyncer.common.util.CollectionUtils;
+import org.dbsyncer.connector.sqlserver.Lsn;
+import org.dbsyncer.connector.sqlserver.SqlServerChangeTable;
+import org.junit.Assert;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.*;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.regex.Matcher;
+
+/**
+ * @version 1.0.0
+ * @Author AE86
+ * @Date 2021-06-14 01:55
+ * @Link https://www.red-gate.com/simple-talk/sql/learn-sql-server/introduction-to-change-data-capture-cdc-in-sql-server-2008/
+ */
+public class ChangeDataCaptureTest {
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    private static final String STATEMENTS_PLACEHOLDER = "#";
+    private static final String GET_DATABASE_NAME = "SELECT db_name()";
+    private static final String GET_DATABASE_VERSION = "SELECT @@VERSION AS 'SQL Server Version'";
+    private static final String GET_TABLE_LIST = "SELECT NAME FROM SYS.TABLES WHERE SCHEMA_ID = SCHEMA_ID('#') AND IS_MS_SHIPPED = 0";
+    private static final String IS_SERVER_AGENT_RUNNING = "EXEC master.#.xp_servicecontrol N'QUERYSTATE', N'SQLSERVERAGENT'";
+    private static final String IS_DB_CDC_ENABLED = "SELECT is_cdc_enabled FROM sys.databases WHERE name = '#'";
+    private static final String IS_TABLE_CDC_ENABLED = "SELECT COUNT(*) FROM sys.tables tb WHERE tb.is_tracked_by_cdc = 1 AND tb.name='#'";
+    private static final String ENABLE_DB_CDC = "IF EXISTS(select 1 from sys.databases where name = '#' AND is_cdc_enabled=0) EXEC sys.sp_cdc_enable_db";
+    private static final String ENABLE_TABLE_CDC = "IF EXISTS(select 1 from sys.tables where name = '#' AND is_tracked_by_cdc=0) EXEC sys.sp_cdc_enable_table @source_schema = N'%s', @source_name = N'#', @role_name = NULL, @supports_net_changes = 0";
+    private static final String DISABLE_TABLE_CDC = "EXEC sys.sp_cdc_disable_table @source_schema = N'%s', @source_name = N'#', @capture_instance = 'all'";
+
+    private static final String AT_TIME_ZONE_UTC = " AT TIME ZONE 'UTC'";
+    private static final String GET_ALL_CHANGES_FOR_TABLE = "SELECT sys.fn_cdc_map_lsn_to_time([__$start_lsn])#, * FROM cdc.[fn_cdc_get_all_changes_#](?, ?, N'all update old') order by [__$start_lsn] ASC, [__$seqval] ASC, [__$operation] ASC";
+    private static final String GET_TABLES_CDC_ENABLED = "EXEC sys.sp_cdc_help_change_data_capture";
+    private static final String GET_MAX_LSN = "SELECT sys.fn_cdc_get_max_lsn()";
+    private static final String GET_MIN_LSN = "SELECT sys.fn_cdc_get_min_lsn('#')";
+    private static final String GET_INCREMENT_LSN = "SELECT sys.fn_cdc_increment_lsn(?)";
+
+    private String realDatabaseName;
+    private String getAllChangesForTable;
+    private Connection connection = null;
+    private Set<String> tables;
+    private String schema;
+
+    /**
+     * <p>cdc.captured_columns – 此表返回捕获列列表的结果。</p>
+     * <p>cdc.change_tables – 此表返回所有启用捕获的表的列表。</p>
+     * <p>cdc.ddl_history – 此表包含自启用捕获数据以来所有 DDL 更改的历史记录。</p>
+     * <p>cdc.index_columns – 该表包含与变更表相关的索引。</p>
+     * <p>cdc.lsn_time_mapping – 此表映射 LSN编号(唯一序列号标识, 增加数字) 和时间。</p>
+     * <p>cdc.fn_cdc_get_all_changes_MY_USER - 可用于获取在特定时间段内发生的事件</p>
+     * <p>sys.fn_cdc_map_time_to_lsn - 表中是否有 tran_end_time值大于或等于指定时间的行。例如,可以用此查询来确定捕获进程是否已处理完截至前指定时间提交的更改</p>
+     * <p>sys.fn_cdc_get_max_lsn</p>
+     * <p>sys.sp_cdc_cleanup_change_table 默认情况下间隔为3天清理日志数据</p>
+     *
+     * @throws SQLException
+     */
+    @Test
+    public void testConnect() throws SQLException, InterruptedException {
+        ChangeDataCaptureTest cdc = new ChangeDataCaptureTest();
+        cdc.start();
+
+        // 获取数据库名 test
+        realDatabaseName = cdc.queryAndMap(GET_DATABASE_NAME, rs -> rs.getString(1));
+        logger.info("数据库名:{}", realDatabaseName);
+        // As per https://www.mssqltips.com/sqlservertip/1140/how-to-tell-what-sql-server-version-you-are-running/
+        // Always beginning with 'Microsoft SQL Server NNNN' but only in case SQL Server is standalone
+        String version = cdc.queryAndMap(GET_DATABASE_VERSION, rs -> rs.getString(1));
+        boolean supportsAtTimeZone = false;
+        if (version.startsWith("Microsoft SQL Server ")) {
+            supportsAtTimeZone = 2016 < Integer.valueOf(version.substring(21, 25));
+        }
+        logger.info("数据库版本:{}", version);
+        tables = cdc.queryAndMapList(GET_TABLE_LIST.replace(STATEMENTS_PLACEHOLDER, realDatabaseName), rs -> {
+            Set<String> table = new LinkedHashSet<>();
+            while (rs.next()) {
+                table.add(rs.getString(1));
+            }
+            return table;
+        });
+        logger.info("所有表:{}", tables);
+        // 获取Agent服务状态 Stopped. Running.
+        boolean enabledServerAgent = cdc.queryAndMap(IS_SERVER_AGENT_RUNNING.replace(STATEMENTS_PLACEHOLDER, realDatabaseName), rs -> "Running.".equals(rs.getString(1)));
+        logger.info("是否启动Agent服务:{}", enabledServerAgent);
+        Assert.assertTrue("The agent server is not running", enabledServerAgent);
+        boolean enabledCDC = cdc.queryAndMap(IS_DB_CDC_ENABLED.replace(STATEMENTS_PLACEHOLDER, realDatabaseName), rs -> rs.getBoolean(1));
+        logger.info("是否启用CDC库[{}]:{}", realDatabaseName, enabledCDC);
+        if (!enabledCDC) {
+            cdc.execute(ENABLE_DB_CDC.replace(STATEMENTS_PLACEHOLDER, realDatabaseName));
+            // make sure DB has cdc-enabled before proceeding
+            TimeUnit.SECONDS.sleep(3);
+        }
+
+        // 注册CDC表
+        tables.forEach(table -> {
+            try {
+                boolean enabledTableCDC = cdc.queryAndMap(IS_TABLE_CDC_ENABLED.replace(STATEMENTS_PLACEHOLDER, table), rs -> rs.getInt(1) > 0);
+                logger.info("是否启用CDC表[{}]:{}", table, enabledTableCDC);
+                if (!enabledTableCDC) {
+                    cdc.execute(String.format(ENABLE_TABLE_CDC.replace(STATEMENTS_PLACEHOLDER, table), schema));
+                    Lsn minLsn = cdc.queryAndMap(GET_MIN_LSN.replace(STATEMENTS_PLACEHOLDER, table), rs -> new Lsn(rs.getBytes(1)));
+                    logger.info("启用CDC表[{}]:{}", table, minLsn.isAvailable());
+                }
+            } catch (SQLException e) {
+                logger.error(e.getMessage());
+            }
+        });
+
+        // 支持UTC
+        getAllChangesForTable = GET_ALL_CHANGES_FOR_TABLE.replaceFirst(STATEMENTS_PLACEHOLDER, Matcher.quoteReplacement(supportsAtTimeZone ? AT_TIME_ZONE_UTC : ""));
+
+        // 读取增量
+        Set<SqlServerChangeTable> changeTables = cdc.queryAndMapList(GET_TABLES_CDC_ENABLED, rs -> {
+            final Set<SqlServerChangeTable> tables = new HashSet<>();
+            while (rs.next()) {
+                SqlServerChangeTable changeTable = new SqlServerChangeTable(
+                        // schemaName
+                        rs.getString(1),
+                        // tableName
+                        rs.getString(2),
+                        // captureInstance
+                        rs.getString(3),
+                        // changeTableObjectId
+                        rs.getInt(4),
+                        // startLsn
+                        rs.getBytes(6),
+                        // stopLsn
+                        rs.getBytes(7),
+                        // capturedColumns
+                        rs.getString(15));
+                logger.info(changeTable.toString());
+                tables.add(changeTable);
+            }
+            return tables;
+        });
+        logger.info("监听表数:{} ", changeTables.size());
+
+        if (!CollectionUtils.isEmpty(changeTables)) {
+            AtomicInteger count = new AtomicInteger(0);
+            Lsn lastLsn = cdc.queryAndMap(GET_MAX_LSN, rs -> new Lsn(rs.getBytes(1)));
+
+            while (true && count.getAndAdd(1) < 30) {
+                Lsn stopLsn = cdc.queryAndMap(GET_MAX_LSN, rs -> new Lsn(rs.getBytes(1)));
+                if (!stopLsn.isAvailable()) {
+                    logger.warn("No maximum LSN recorded in the database; please ensure that the SQL Server Agent is running");
+                    cdc.pause();
+                    continue;
+                }
+
+                if (stopLsn.compareTo(lastLsn) <= 0) {
+                    cdc.pause();
+                    continue;
+                }
+
+                Lsn startLsn = getIncrementLsn(cdc, lastLsn);
+                changeTables.forEach(changeTable -> {
+                    try {
+                        final String query = getAllChangesForTable.replace(STATEMENTS_PLACEHOLDER, changeTable.getCaptureInstance());
+                        logger.info("Getting changes for table {} in range[{}, {}]", changeTable.getTableName(), startLsn, stopLsn);
+
+                        cdc.queryAndMapList(query, statement -> {
+                            statement.setBytes(1, startLsn.getBinary());
+                            statement.setBytes(2, stopLsn.getBinary());
+                        }, rs -> {
+                            int columnCount = rs.getMetaData().getColumnCount();
+                            List<List<Object>> data = new ArrayList<>(columnCount);
+                            List<Object> row = null;
+                            while (rs.next()) {
+                                row = new ArrayList<>(columnCount);
+                                for (int i = 1; i <= columnCount; i++) {
+                                    row.add(rs.getObject(i));
+                                }
+                                logger.info("rows:{}", row);
+                                data.add(row);
+                            }
+                            return data;
+                        });
+                    } catch (Exception e) {
+                        e.printStackTrace();
+                    }
+                });
+
+                lastLsn = stopLsn;
+            }
+        }
+
+        // 注销CDC表
+        for (String table : tables) {
+            cdc.execute(String.format(DISABLE_TABLE_CDC.replace(STATEMENTS_PLACEHOLDER, table), schema));
+        }
+        cdc.close();
+    }
+
+    public void start() throws SQLException {
+        String username = "sa";
+        String password = "123";
+        String url = "jdbc:sqlserver://127.0.0.1:1433;DatabaseName=test";
+        schema = "dbo";
+        connection = DriverManager.getConnection(url, username, password);
+        if (connection != null) {
+            DatabaseMetaData dm = (DatabaseMetaData) connection.getMetaData();
+            logger.info("Driver name: " + dm.getDriverName());
+            logger.info("Driver version: " + dm.getDriverVersion());
+            logger.info("Product name: " + dm.getDatabaseProductName());
+            logger.info("Product version: " + dm.getDatabaseProductVersion());
+        }
+    }
+
+    public void close() {
+        if (null != connection) {
+            close(connection);
+        }
+    }
+
+    private void close(AutoCloseable closeable) {
+        if (null != closeable) {
+            try {
+                closeable.close();
+            } catch (Exception e) {
+                logger.error(e.getMessage());
+            }
+        }
+    }
+
+    private Lsn getIncrementLsn(ChangeDataCaptureTest cdc, Lsn lastLsn) {
+        return cdc.queryAndMap(GET_INCREMENT_LSN, statement -> statement.setBytes(1, lastLsn.getBinary()), rs -> Lsn.valueOf(rs.getBytes(1)));
+    }
+
+    private void pause() throws InterruptedException {
+        TimeUnit.SECONDS.sleep(2);
+    }
+
+    private void execute(String... sqlStatements) throws SQLException {
+        Statement statement = connection.createStatement();
+        try {
+            for (String sqlStatement : sqlStatements) {
+                if (sqlStatement != null) {
+                    logger.info("executing '{}'", sqlStatement);
+                    statement.execute(sqlStatement);
+                }
+            }
+        } catch (Exception e) {
+            logger.error(e.getMessage());
+        } finally {
+            close(statement);
+        }
+    }
+
+    public interface ResultSetMapper<T> {
+        T apply(ResultSet rs) throws SQLException;
+    }
+
+    public interface StatementPreparer {
+        void accept(PreparedStatement statement) throws SQLException;
+    }
+
+    public <T> T queryAndMap(String sql, ResultSetMapper<T> mapper) {
+        return queryAndMap(sql, null, mapper);
+    }
+
+    public <T> T queryAndMap(String sql, StatementPreparer statementPreparer, ResultSetMapper<T> mapper) {
+        PreparedStatement ps = null;
+        ResultSet rs = null;
+        T apply = null;
+        try {
+            ps = connection.prepareStatement(sql);
+            if (null != statementPreparer) {
+                statementPreparer.accept(ps);
+            }
+            rs = ps.executeQuery();
+            if (rs.next()) {
+                apply = mapper.apply(rs);
+            }
+        } catch (Exception e) {
+            logger.error(e.getMessage());
+        } finally {
+            close(rs);
+            close(ps);
+        }
+        return apply;
+    }
+
+    public <T> T queryAndMapList(String sql, ResultSetMapper<T> mapper) {
+        return queryAndMapList(sql, null, mapper);
+    }
+
+    public <T> T queryAndMapList(String sql, StatementPreparer statementPreparer, ResultSetMapper<T> mapper) {
+        PreparedStatement ps = null;
+        ResultSet rs = null;
+        T apply = null;
+        try {
+            ps = connection.prepareStatement(sql);
+            if (null != statementPreparer) {
+                statementPreparer.accept(ps);
+            }
+            rs = ps.executeQuery();
+            apply = mapper.apply(rs);
+        } catch (SQLServerException e) {
+            // 为过程或函数 cdc.fn_cdc_get_all_changes_ ...  提供的参数数目不足。
+            logger.warn(e.getMessage());
+        } catch (Exception e) {
+            logger.error(e.getMessage());
+        } finally {
+            close(rs);
+            close(ps);
+        }
+        return apply;
+    }
+
 }

+ 100 - 100
dbsyncer-listener/src/main/test/DBChangeNotificationTest.java → dbsyncer-connector/src/main/test/DBChangeNotificationTest.java

@@ -1,101 +1,101 @@
-import oracle.jdbc.OracleStatement;
-import oracle.jdbc.driver.OracleConnection;
-import org.dbsyncer.listener.oracle.dcn.DBChangeNotification;
-import org.junit.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.*;
-
-/**
- * @version 1.0.0
- * @Author AE86
- * @Date 2021-05-10 22:25
- */
-public class DBChangeNotificationTest {
-
-    private final Logger logger = LoggerFactory.getLogger(getClass());
-
-    @Test
-    public void testConnect() throws Exception {
-        String username = "ae86";
-        String password = "123";
-        String url = "jdbc:oracle:thin:@127.0.0.1:1521:XE";
-
-        final DBChangeNotification dcn = new DBChangeNotification(username, password, url);
-        dcn.addRowEventListener((e) ->
-            logger.info("{}触发{}, data:{}", e.getSourceTableName(), e.getEvent(), e.getDataList())
-        );
-        dcn.start();
-
-        // 模拟并发
-        final int threadSize = 301;
-        final ExecutorService pool = Executors.newFixedThreadPool(threadSize);
-        final CyclicBarrier barrier = new CyclicBarrier(threadSize);
-        final CountDownLatch latch = new CountDownLatch(threadSize);
-
-        for (int i = 0; i < threadSize; i++) {
-            final int k = i + 3;
-            pool.submit(() -> {
-                try {
-                    barrier.await();
-                    //read(k, dcn);
-
-                    // 模拟写入操作
-                    insert(k, dcn);
-
-                } catch (InterruptedException e) {
-                    logger.error(e.getMessage());
-                } catch (BrokenBarrierException e) {
-                    logger.error(e.getMessage());
-                } finally {
-                    latch.countDown();
-                }
-            });
-        }
-
-        try {
-            latch.await();
-            logger.info("try to close");
-        } catch (InterruptedException e) {
-            logger.error(e.getMessage());
-        }
-        pool.shutdown();
-
-        TimeUnit.SECONDS.sleep(20);
-        dcn.close();
-        logger.info("test end");
-
-    }
-
-    private void insert(int k, DBChangeNotification dcn) {
-        OracleConnection conn = dcn.getOracleConnection();
-        OracleStatement os = null;
-        ResultSet rs = null;
-        try {
-            os = (OracleStatement) conn.createStatement();
-            String sql = "INSERT INTO \"AE86\".\"my_user\"(\"id\", \"name\", \"age\", \"phone\", \"create_date\", \"last_time\", \"money\", \"car\", \"big\", \"clo\", \"rel\") VALUES (" + k + ", '红包', '2', '18200001111', TO_DATE('2015-10-23 00:00:00', 'SYYYY-MM-DD HH24:MI:SS'), TO_TIMESTAMP('2021-01-23 00:00:00.000000', 'SYYYY-MM-DD HH24:MI:SS:FF6'), '200.00000000000000', '4', null, '888', '3.0000000000000000')";
-
-            int i = os.executeUpdate(sql);
-            logger.info("insert:{}, {}", k, i);
-        } catch (SQLException e) {
-            logger.error(e.getMessage());
-        } finally {
-            dcn.close(rs);
-            dcn.close(os);
-        }
-    }
-
-    private void read(final int k, DBChangeNotification dcn) {
-        final String tableName = "my_user";
-        final String rowId = "AAAE5fAABAAALCJAAx";
-        List<Object> data = new ArrayList<>();
-        dcn.read(tableName, rowId, data);
-        logger.info("{}, 【{}】, data:{}", k, data.size(), data);
-    }
-
+import oracle.jdbc.OracleStatement;
+import oracle.jdbc.driver.OracleConnection;
+import org.dbsyncer.connector.oracle.dcn.DBChangeNotification;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.*;
+
+/**
+ * @version 1.0.0
+ * @Author AE86
+ * @Date 2021-05-10 22:25
+ */
+public class DBChangeNotificationTest {
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    @Test
+    public void testConnect() throws Exception {
+        String username = "ae86";
+        String password = "123";
+        String url = "jdbc:oracle:thin:@127.0.0.1:1521:XE";
+
+        final DBChangeNotification dcn = new DBChangeNotification(username, password, url);
+        dcn.addRowEventListener((e) ->
+            logger.info("{}触发{}, data:{}", e.getSourceTableName(), e.getEvent(), e.getDataList())
+        );
+        dcn.start();
+
+        // 模拟并发
+        final int threadSize = 301;
+        final ExecutorService pool = Executors.newFixedThreadPool(threadSize);
+        final CyclicBarrier barrier = new CyclicBarrier(threadSize);
+        final CountDownLatch latch = new CountDownLatch(threadSize);
+
+        for (int i = 0; i < threadSize; i++) {
+            final int k = i + 3;
+            pool.submit(() -> {
+                try {
+                    barrier.await();
+                    //read(k, dcn);
+
+                    // 模拟写入操作
+                    insert(k, dcn);
+
+                } catch (InterruptedException e) {
+                    logger.error(e.getMessage());
+                } catch (BrokenBarrierException e) {
+                    logger.error(e.getMessage());
+                } finally {
+                    latch.countDown();
+                }
+            });
+        }
+
+        try {
+            latch.await();
+            logger.info("try to close");
+        } catch (InterruptedException e) {
+            logger.error(e.getMessage());
+        }
+        pool.shutdown();
+
+        TimeUnit.SECONDS.sleep(20);
+        dcn.close();
+        logger.info("test end");
+
+    }
+
+    private void insert(int k, DBChangeNotification dcn) {
+        OracleConnection conn = dcn.getOracleConnection();
+        OracleStatement os = null;
+        ResultSet rs = null;
+        try {
+            os = (OracleStatement) conn.createStatement();
+            String sql = "INSERT INTO \"AE86\".\"my_user\"(\"id\", \"name\", \"age\", \"phone\", \"create_date\", \"last_time\", \"money\", \"car\", \"big\", \"clo\", \"rel\") VALUES (" + k + ", '红包', '2', '18200001111', TO_DATE('2015-10-23 00:00:00', 'SYYYY-MM-DD HH24:MI:SS'), TO_TIMESTAMP('2021-01-23 00:00:00.000000', 'SYYYY-MM-DD HH24:MI:SS:FF6'), '200.00000000000000', '4', null, '888', '3.0000000000000000')";
+
+            int i = os.executeUpdate(sql);
+            logger.info("insert:{}, {}", k, i);
+        } catch (SQLException e) {
+            logger.error(e.getMessage());
+        } finally {
+            dcn.close(rs);
+            dcn.close(os);
+        }
+    }
+
+    private void read(final int k, DBChangeNotification dcn) {
+        final String tableName = "my_user";
+        final String rowId = "AAAE5fAABAAALCJAAx";
+        List<Object> data = new ArrayList<>();
+        dcn.read(tableName, rowId, data);
+        logger.info("{}, 【{}】, data:{}", k, data.size(), data);
+    }
+
 }

+ 0 - 0
dbsyncer-listener/src/main/test/ESClientTest.java → dbsyncer-connector/src/main/test/ESClientTest.java


+ 0 - 0
dbsyncer-listener/src/main/test/FileWatchTest.java → dbsyncer-connector/src/main/test/FileWatchTest.java


+ 0 - 0
dbsyncer-listener/src/main/test/KafkaClientTest.java → dbsyncer-connector/src/main/test/KafkaClientTest.java


+ 85 - 85
dbsyncer-listener/src/main/test/LinkedBlockingQueueTest.java → dbsyncer-connector/src/main/test/LinkedBlockingQueueTest.java

@@ -1,86 +1,86 @@
-import oracle.jdbc.dcn.TableChangeDescription;
-import org.dbsyncer.common.util.RandomUtil;
-import org.dbsyncer.listener.oracle.event.DCNEvent;
-import org.junit.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-public class LinkedBlockingQueueTest {
-
-    private final Logger        logger = LoggerFactory.getLogger(getClass());
-    private       BlockingQueue queue  = new LinkedBlockingQueue<>(10);
-
-    @Test
-    public void testProducerAndConsumer() throws InterruptedException {
-        logger.info("test begin");
-        new Producer(queue).start();
-        new Consumer(queue).start();
-        new Consumer(queue).start();
-        TimeUnit.SECONDS.sleep(60);
-        logger.info("test end");
-    }
-
-    /**
-     * 生产
-     */
-    class Producer extends Thread {
-
-        BlockingQueue<DCNEvent> queue;
-        int taskNumber = 50;
-
-        public Producer(BlockingQueue<DCNEvent> queue) {
-            setName("Producer-thread");
-            this.queue = queue;
-        }
-
-        @Override
-        public void run() {
-            logger.info("生产线程{}开始工作", Thread.currentThread().getName());
-            for (int i = 0; i < taskNumber; i++) {
-                DCNEvent event = new DCNEvent("my_user" + i, "AAAF8BAABAAALJBAAA", TableChangeDescription.TableOperation.INSERT.getCode());
-                try {
-                    // 如果BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续
-                    queue.put(event);
-                } catch (InterruptedException e) {
-                    logger.error("添加消息:{}, 失败", event, e.getMessage());
-                }
-            }
-            logger.info("生产线程{}结束工作", Thread.currentThread().getName());
-        }
-    }
-
-    /**
-     * 消费
-     */
-    class Consumer extends Thread {
-
-        BlockingQueue<DCNEvent> queue;
-
-        public Consumer(BlockingQueue<DCNEvent> queue) {
-            setName("Consumer-thread-" + RandomUtil.nextInt(1, 100));
-            this.queue = queue;
-        }
-
-        @Override
-        public void run() {
-            String threadName = Thread.currentThread().getName();
-            logger.info("消费线程{}开始工作", threadName);
-            while (true) {
-                try {
-                    // 模拟耗时
-                    TimeUnit.SECONDS.sleep(RandomUtil.nextInt(0, 3));
-                    // 取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到Blocking有新的对象被加入为止
-                    DCNEvent event = queue.take();
-                    logger.error("消费线程{}接受消息:{}", threadName, event.getTableName());
-                } catch (InterruptedException e) {
-                    logger.error(e.getMessage());
-                }
-            }
-        }
-    }
-
+import oracle.jdbc.dcn.TableChangeDescription;
+import org.dbsyncer.common.util.RandomUtil;
+import org.dbsyncer.connector.oracle.DCNEvent;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class LinkedBlockingQueueTest {
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+    private BlockingQueue queue = new LinkedBlockingQueue<>(10);
+
+    @Test
+    public void testProducerAndConsumer() throws InterruptedException {
+        logger.info("test begin");
+        new Producer(queue).start();
+        new Consumer(queue).start();
+        new Consumer(queue).start();
+        TimeUnit.SECONDS.sleep(60);
+        logger.info("test end");
+    }
+
+    /**
+     * 生产
+     */
+    class Producer extends Thread {
+
+        BlockingQueue<DCNEvent> queue;
+        int taskNumber = 50;
+
+        public Producer(BlockingQueue<DCNEvent> queue) {
+            setName("Producer-thread");
+            this.queue = queue;
+        }
+
+        @Override
+        public void run() {
+            logger.info("生产线程{}开始工作", Thread.currentThread().getName());
+            for (int i = 0; i < taskNumber; i++) {
+                DCNEvent event = new DCNEvent("my_user" + i, "AAAF8BAABAAALJBAAA", TableChangeDescription.TableOperation.INSERT.getCode());
+                try {
+                    // 如果BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续
+                    queue.put(event);
+                } catch (InterruptedException e) {
+                    logger.error("添加消息:{}, 失败", event, e.getMessage());
+                }
+            }
+            logger.info("生产线程{}结束工作", Thread.currentThread().getName());
+        }
+    }
+
+    /**
+     * 消费
+     */
+    class Consumer extends Thread {
+
+        BlockingQueue<DCNEvent> queue;
+
+        public Consumer(BlockingQueue<DCNEvent> queue) {
+            setName("Consumer-thread-" + RandomUtil.nextInt(1, 100));
+            this.queue = queue;
+        }
+
+        @Override
+        public void run() {
+            String threadName = Thread.currentThread().getName();
+            logger.info("消费线程{}开始工作", threadName);
+            while (true) {
+                try {
+                    // 模拟耗时
+                    TimeUnit.SECONDS.sleep(RandomUtil.nextInt(0, 3));
+                    // 取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到Blocking有新的对象被加入为止
+                    DCNEvent event = queue.take();
+                    logger.error("消费线程{}接受消息:{}", threadName, event.getTableName());
+                } catch (InterruptedException e) {
+                    logger.error(e.getMessage());
+                }
+            }
+        }
+    }
+
 }

+ 0 - 0
dbsyncer-listener/src/main/test/PGReplicationTest.java → dbsyncer-connector/src/main/test/PGReplicationTest.java


+ 0 - 43
dbsyncer-listener/pom.xml

@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <parent>
-        <artifactId>dbsyncer</artifactId>
-        <groupId>org.ghi</groupId>
-        <version>2.0.0</version>
-    </parent>
-    <modelVersion>4.0.0</modelVersion>
-    <artifactId>dbsyncer-listener</artifactId>
-
-    <dependencies>
-        <!-- Connector 连接器 -->
-        <dependency>
-            <groupId>org.ghi</groupId>
-            <artifactId>dbsyncer-connector</artifactId>
-            <version>${project.parent.version}</version>
-        </dependency>
-
-        <dependency>
-            <groupId>com.github.jsqlparser</groupId>
-            <artifactId>jsqlparser</artifactId>
-        </dependency>
-
-        <dependency>
-            <groupId>com.zendesk</groupId>
-            <artifactId>mysql-binlog-connector-java</artifactId>
-        </dependency>
-
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-log4j2</artifactId>
-        </dependency>
-
-        <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
-            <scope>provided</scope>
-        </dependency>
-
-    </dependencies>
-</project>

+ 0 - 9
dbsyncer-listener/src/main/java/org/dbsyncer/listener/Listener.java

@@ -1,9 +0,0 @@
-package org.dbsyncer.listener;
-
-import org.dbsyncer.listener.enums.ListenerTypeEnum;
-
-public interface Listener {
-
-    <T> T getExtractor(ListenerTypeEnum listenerTypeEnum, String connectorType, Class<T> valueType) throws IllegalAccessException, InstantiationException;
-
-}

+ 0 - 27
dbsyncer-listener/src/main/java/org/dbsyncer/listener/ListenerException.java

@@ -1,27 +0,0 @@
-package org.dbsyncer.listener;
-
-/**
- * @author AE86
- * @version 1.0.0
- * @date 2019/9/28 22:39
- */
-public class ListenerException extends RuntimeException {
-
-	private static final long serialVersionUID = 1L;
-
-	public ListenerException(String message) {
-        super(message);
-    }
-
-    public ListenerException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    public ListenerException(Throwable cause) {
-        super(cause);
-    }
-
-    protected ListenerException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
-        super(message, cause, enableSuppression, writableStackTrace);
-    }
-}

+ 0 - 26
dbsyncer-listener/src/main/java/org/dbsyncer/listener/ListenerFactory.java

@@ -1,26 +0,0 @@
-package org.dbsyncer.listener;
-
-import org.dbsyncer.listener.enums.ListenerTypeEnum;
-import org.springframework.stereotype.Component;
-
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.function.Function;
-
-@Component
-public class ListenerFactory implements Listener {
-
-    private Map<ListenerTypeEnum, Function<String, Class>> map = new LinkedHashMap<>();
-
-    @Override
-    public <T> T getExtractor(ListenerTypeEnum listenerTypeEnum, String connectorType, Class<T> valueType) throws IllegalAccessException, InstantiationException {
-        Function function = map.get(listenerTypeEnum);
-        if (null == function) {
-            throw new ListenerException(String.format("Unsupported type \"%s\" for extractor \"%s\".", listenerTypeEnum, connectorType));
-        }
-
-        Class<T> clazz = (Class<T>) function.apply(connectorType);
-        return clazz.newInstance();
-    }
-
-}

+ 0 - 19
dbsyncer-listener/src/main/java/org/dbsyncer/listener/config/Host.java

@@ -1,19 +0,0 @@
-package org.dbsyncer.listener.config;
-
-public class Host {
-    private String ip;
-    private int    port;
-
-    public Host(String ip, int port) {
-        this.ip = ip;
-        this.port = port;
-    }
-
-    public String getIp() {
-        return ip;
-    }
-
-    public int getPort() {
-        return port;
-    }
-}

+ 0 - 22
dbsyncer-listener/src/main/java/org/dbsyncer/listener/kafka/KafkaExtractor.java

@@ -1,22 +0,0 @@
-package org.dbsyncer.listener.kafka;
-
-import org.dbsyncer.listener.AbstractExtractor;
-import org.dbsyncer.listener.ListenerException;
-
-/**
- * @author AE86
- * @version 1.0.0
- * @date 2021/12/18 22:19
- */
-public class KafkaExtractor extends AbstractExtractor {
-
-    @Override
-    public void start() {
-        throw new ListenerException("抱歉,kafka监听功能正在开发中...");
-    }
-
-    @Override
-    public void close() {
-
-    }
-}

+ 43 - 45
dbsyncer-manager/src/main/java/org/dbsyncer/manager/impl/IncrementPuller.java

@@ -1,17 +1,13 @@
 package org.dbsyncer.manager.impl;
 
 import org.dbsyncer.common.util.CollectionUtils;
+import org.dbsyncer.connector.AbstractListener;
 import org.dbsyncer.connector.ConnectorFactory;
+import org.dbsyncer.connector.config.ListenerConfig;
+import org.dbsyncer.connector.quartz.AbstractQuartzListener;
+import org.dbsyncer.connector.quartz.TableGroupQuartzCommand;
 import org.dbsyncer.connector.scheduled.ScheduledTaskJob;
 import org.dbsyncer.connector.scheduled.ScheduledTaskService;
-import org.dbsyncer.listener.AbstractExtractor;
-import org.dbsyncer.listener.Extractor;
-import org.dbsyncer.listener.Listener;
-import org.dbsyncer.listener.config.ListenerConfig;
-import org.dbsyncer.listener.enums.ListenerTypeEnum;
-import org.dbsyncer.listener.model.ChangedOffset;
-import org.dbsyncer.listener.quartz.AbstractQuartzExtractor;
-import org.dbsyncer.listener.quartz.TableGroupQuartzCommand;
 import org.dbsyncer.manager.AbstractPuller;
 import org.dbsyncer.manager.ManagerException;
 import org.dbsyncer.parser.LogService;
@@ -25,6 +21,9 @@ import org.dbsyncer.parser.model.Connector;
 import org.dbsyncer.parser.model.Mapping;
 import org.dbsyncer.parser.model.Meta;
 import org.dbsyncer.parser.model.TableGroup;
+import org.dbsyncer.sdk.enums.ListenerTypeEnum;
+import org.dbsyncer.sdk.listener.Listener;
+import org.dbsyncer.sdk.model.ChangedOffset;
 import org.dbsyncer.sdk.model.ConnectorConfig;
 import org.dbsyncer.sdk.model.Table;
 import org.slf4j.Logger;
@@ -57,13 +56,7 @@ public final class IncrementPuller extends AbstractPuller implements Application
     private final Logger logger = LoggerFactory.getLogger(getClass());
 
     @Resource
-    private Listener listener;
-
-    @Resource
-    private ProfileComponent profileComponent;
-
-    @Resource
-    private LogService logService;
+    private BufferActuatorRouter bufferActuatorRouter;
 
     @Resource
     private ScheduledTaskService scheduledTaskService;
@@ -72,9 +65,12 @@ public final class IncrementPuller extends AbstractPuller implements Application
     private ConnectorFactory connectorFactory;
 
     @Resource
-    private BufferActuatorRouter bufferActuatorRouter;
+    private ProfileComponent profileComponent;
 
-    private Map<String, Extractor> map = new ConcurrentHashMap<>();
+    @Resource
+    private LogService logService;
+
+    private Map<String, Listener> map = new ConcurrentHashMap<>();
 
     @PostConstruct
     private void init() {
@@ -99,7 +95,7 @@ public final class IncrementPuller extends AbstractPuller implements Application
                 meta.setBeginTime(now);
                 meta.setEndTime(now);
                 profileComponent.editConfigModel(meta);
-                map.putIfAbsent(metaId, getExtractor(mapping, connector, list, meta));
+                map.putIfAbsent(metaId, getListener(mapping, connector, list, meta));
                 map.get(metaId).start();
             } catch (Exception e) {
                 close(metaId);
@@ -114,10 +110,10 @@ public final class IncrementPuller extends AbstractPuller implements Application
 
     @Override
     public void close(String metaId) {
-        Extractor extractor = map.get(metaId);
-        if (null != extractor) {
+        Listener listener = map.get(metaId);
+        if (null != listener) {
             bufferActuatorRouter.unbind(metaId);
-            extractor.close();
+            listener.close();
         }
         map.remove(metaId);
         publishClosedEvent(metaId);
@@ -139,31 +135,34 @@ public final class IncrementPuller extends AbstractPuller implements Application
     @Override
     public void run() {
         // 定时同步增量信息
-        map.values().forEach(extractor -> extractor.flushEvent());
+        map.values().forEach(listener -> listener.flushEvent());
     }
 
-    private AbstractExtractor getExtractor(Mapping mapping, Connector connector, List<TableGroup> list, Meta meta) throws InstantiationException, IllegalAccessException {
+    private Listener getListener(Mapping mapping, Connector connector, List<TableGroup> list, Meta meta) {
         ConnectorConfig connectorConfig = connector.getConfig();
         ListenerConfig listenerConfig = mapping.getListener();
-        // timing/log
-        final String listenerType = listenerConfig.getListenerType();
+        String listenerType = listenerConfig.getListenerType();
+
+        Listener listener = connectorFactory.getListener(connectorConfig.getConnectorType(), listenerType);
+        if (null == listener) {
+            throw new ManagerException(String.format("Unsupported listener type \"%s\".", connectorConfig.getConnectorType()));
+        }
 
-        AbstractExtractor extractor = null;
         // 默认定时抽取
-        if (ListenerTypeEnum.isTiming(listenerType)) {
-            AbstractQuartzExtractor quartzExtractor = listener.getExtractor(ListenerTypeEnum.TIMING, connectorConfig.getConnectorType(), AbstractQuartzExtractor.class);
-            quartzExtractor.setCommands(list.stream().map(t -> new TableGroupQuartzCommand(t.getSourceTable(), t.getCommand())).collect(Collectors.toList()));
-            quartzExtractor.register(new QuartzConsumer().init(bufferActuatorRouter, profileComponent, logService, meta, mapping, list));
-            extractor = quartzExtractor;
+        if (ListenerTypeEnum.isTiming(listenerType) && listener instanceof AbstractQuartzListener) {
+            AbstractQuartzListener quartzListener = (AbstractQuartzListener) listener;
+            quartzListener.setCommands(list.stream().map(t -> new TableGroupQuartzCommand(t.getSourceTable(), t.getCommand())).collect(Collectors.toList()));
+            quartzListener.register(new QuartzConsumer().init(bufferActuatorRouter, profileComponent, logService, meta, mapping, list));
         }
 
         // 基于日志抽取
-        if (ListenerTypeEnum.isLog(listenerType)) {
-            extractor = listener.getExtractor(ListenerTypeEnum.LOG, connectorConfig.getConnectorType(), AbstractExtractor.class);
-            extractor.register(new LogConsumer().init(bufferActuatorRouter, profileComponent, logService, meta, mapping, list));
+        if (ListenerTypeEnum.isLog(listenerType) && listener instanceof AbstractListener) {
+            AbstractListener abstractListener = (AbstractListener) listener;
+            abstractListener.register(new LogConsumer().init(bufferActuatorRouter, profileComponent, logService, meta, mapping, list));
         }
 
-        if (null != extractor) {
+        if (listener instanceof AbstractListener) {
+            AbstractListener abstractListener = (AbstractListener) listener;
             Set<String> filterTable = new HashSet<>();
             List<Table> sourceTable = new ArrayList<>();
             list.forEach(t -> {
@@ -174,18 +173,17 @@ public final class IncrementPuller extends AbstractPuller implements Application
                 filterTable.add(table.getName());
             });
 
-            extractor.setConnectorFactory(connectorFactory);
-            extractor.setScheduledTaskService(scheduledTaskService);
-            extractor.setConnectorConfig(connectorConfig);
-            extractor.setListenerConfig(listenerConfig);
-            extractor.setFilterTable(filterTable);
-            extractor.setSourceTable(sourceTable);
-            extractor.setSnapshot(meta.getSnapshot());
-            extractor.setMetaId(meta.getId());
-            return extractor;
+            abstractListener.setConnectorFactory(connectorFactory);
+            abstractListener.setScheduledTaskService(scheduledTaskService);
+            abstractListener.setConnectorConfig(connectorConfig);
+            abstractListener.setListenerConfig(listenerConfig);
+            abstractListener.setFilterTable(filterTable);
+            abstractListener.setSourceTable(sourceTable);
+            abstractListener.setSnapshot(meta.getSnapshot());
+            abstractListener.setMetaId(meta.getId());
         }
 
-        throw new ManagerException("未知的监听配置.");
+        return listener;
     }
 
 }

+ 0 - 7
dbsyncer-parser/pom.xml

@@ -11,13 +11,6 @@
     <artifactId>dbsyncer-parser</artifactId>
 
     <dependencies>
-        <!-- Listener 监听器 -->
-        <dependency>
-            <groupId>org.ghi</groupId>
-            <artifactId>dbsyncer-listener</artifactId>
-            <version>${project.parent.version}</version>
-        </dependency>
-
         <!-- Plugin 插件服务 -->
         <dependency>
             <groupId>org.ghi</groupId>

+ 1 - 1
dbsyncer-parser/src/main/java/org/dbsyncer/parser/ProfileComponent.java

@@ -4,7 +4,7 @@
 package org.dbsyncer.parser;
 
 import org.dbsyncer.connector.enums.FilterEnum;
-import org.dbsyncer.listener.enums.QuartzFilterEnum;
+import org.dbsyncer.connector.enums.QuartzFilterEnum;
 import org.dbsyncer.parser.enums.ConvertEnum;
 import org.dbsyncer.parser.model.ConfigModel;
 import org.dbsyncer.parser.model.Connector;

+ 3 - 3
dbsyncer-parser/src/main/java/org/dbsyncer/parser/consumer/AbstractConsumer.java

@@ -3,9 +3,9 @@
  */
 package org.dbsyncer.parser.consumer;
 
-import org.dbsyncer.listener.ChangedEvent;
-import org.dbsyncer.listener.event.DDLChangedEvent;
-import org.dbsyncer.listener.Watcher;
+import org.dbsyncer.sdk.listener.ChangedEvent;
+import org.dbsyncer.sdk.listener.event.DDLChangedEvent;
+import org.dbsyncer.sdk.listener.Watcher;
 import org.dbsyncer.parser.ProfileComponent;
 import org.dbsyncer.parser.flush.impl.BufferActuatorRouter;
 import org.dbsyncer.parser.LogService;

+ 3 - 3
dbsyncer-parser/src/main/java/org/dbsyncer/parser/consumer/impl/LogConsumer.java

@@ -3,9 +3,9 @@
  */
 package org.dbsyncer.parser.consumer.impl;
 
-import org.dbsyncer.listener.event.CommonChangedEvent;
-import org.dbsyncer.listener.event.DDLChangedEvent;
-import org.dbsyncer.listener.event.RowChangedEvent;
+import org.dbsyncer.sdk.listener.event.CommonChangedEvent;
+import org.dbsyncer.sdk.listener.event.DDLChangedEvent;
+import org.dbsyncer.sdk.listener.event.RowChangedEvent;
 import org.dbsyncer.common.util.CollectionUtils;
 import org.dbsyncer.parser.consumer.AbstractConsumer;
 import org.dbsyncer.parser.model.FieldPicker;

+ 1 - 1
dbsyncer-parser/src/main/java/org/dbsyncer/parser/consumer/impl/QuartzConsumer.java

@@ -3,7 +3,7 @@
  */
 package org.dbsyncer.parser.consumer.impl;
 
-import org.dbsyncer.listener.event.ScanChangedEvent;
+import org.dbsyncer.sdk.listener.event.ScanChangedEvent;
 import org.dbsyncer.parser.consumer.AbstractConsumer;
 import org.dbsyncer.parser.model.FieldPicker;
 import org.dbsyncer.parser.model.TableGroup;

+ 1 - 1
dbsyncer-parser/src/main/java/org/dbsyncer/parser/event/RefreshOffsetEvent.java

@@ -1,6 +1,6 @@
 package org.dbsyncer.parser.event;
 
-import org.dbsyncer.listener.model.ChangedOffset;
+import org.dbsyncer.sdk.model.ChangedOffset;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.event.ApplicationContextEvent;
 

+ 1 - 1
dbsyncer-parser/src/main/java/org/dbsyncer/parser/flush/impl/BufferActuatorRouter.java

@@ -4,7 +4,7 @@
 package org.dbsyncer.parser.flush.impl;
 
 import org.dbsyncer.common.config.TableGroupBufferConfig;
-import org.dbsyncer.listener.ChangedEvent;
+import org.dbsyncer.sdk.listener.ChangedEvent;
 import org.dbsyncer.parser.flush.BufferActuator;
 import org.dbsyncer.parser.model.WriterRequest;
 import org.slf4j.Logger;

+ 1 - 1
dbsyncer-parser/src/main/java/org/dbsyncer/parser/impl/ProfileComponentImpl.java

@@ -7,7 +7,7 @@ import org.dbsyncer.common.util.CollectionUtils;
 import org.dbsyncer.common.util.JsonUtil;
 import org.dbsyncer.connector.ConnectorFactory;
 import org.dbsyncer.connector.enums.FilterEnum;
-import org.dbsyncer.listener.enums.QuartzFilterEnum;
+import org.dbsyncer.connector.enums.QuartzFilterEnum;
 import org.dbsyncer.parser.ProfileComponent;
 import org.dbsyncer.parser.enums.CommandEnum;
 import org.dbsyncer.parser.enums.ConvertEnum;

+ 1 - 1
dbsyncer-parser/src/main/java/org/dbsyncer/parser/model/Mapping.java

@@ -1,6 +1,6 @@
 package org.dbsyncer.parser.model;
 
-import org.dbsyncer.listener.config.ListenerConfig;
+import org.dbsyncer.connector.config.ListenerConfig;
 import org.dbsyncer.sdk.enums.ModelEnum;
 import org.dbsyncer.sdk.model.Field;
 import org.dbsyncer.storage.constant.ConfigConstant;

+ 3 - 3
dbsyncer-parser/src/main/java/org/dbsyncer/parser/model/WriterRequest.java

@@ -1,8 +1,8 @@
 package org.dbsyncer.parser.model;
 
-import org.dbsyncer.listener.ChangedEvent;
-import org.dbsyncer.listener.model.ChangedOffset;
-import org.dbsyncer.listener.event.DDLChangedEvent;
+import org.dbsyncer.sdk.listener.ChangedEvent;
+import org.dbsyncer.sdk.model.ChangedOffset;
+import org.dbsyncer.sdk.listener.event.DDLChangedEvent;
 import org.dbsyncer.parser.flush.BufferRequest;
 
 import java.util.Map;

+ 1 - 1
dbsyncer-parser/src/main/java/org/dbsyncer/parser/model/WriterResponse.java

@@ -1,6 +1,6 @@
 package org.dbsyncer.parser.model;
 
-import org.dbsyncer.listener.model.ChangedOffset;
+import org.dbsyncer.sdk.model.ChangedOffset;
 import org.dbsyncer.common.util.StringUtil;
 import org.dbsyncer.parser.flush.BufferResponse;
 

+ 3 - 0
dbsyncer-sdk/pom.xml

@@ -22,18 +22,21 @@
         <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-jdbc</artifactId>
+            <scope>provided</scope>
         </dependency>
 
         <!-- oracle-driver -->
         <dependency>
             <groupId>com.oracle</groupId>
             <artifactId>ojdbc6</artifactId>
+            <scope>provided</scope>
         </dependency>
 
         <!-- postgresql -->
         <dependency>
             <groupId>org.postgresql</groupId>
             <artifactId>postgresql</artifactId>
+            <scope>provided</scope>
         </dependency>
 
     </dependencies>

+ 1 - 1
dbsyncer-listener/src/main/java/org/dbsyncer/listener/enums/ListenerTypeEnum.java → dbsyncer-sdk/src/main/java/org/dbsyncer/sdk/enums/ListenerTypeEnum.java

@@ -1,4 +1,4 @@
-package org.dbsyncer.listener.enums;
+package org.dbsyncer.sdk.enums;
 
 import org.dbsyncer.common.util.StringUtil;
 

+ 3 - 3
dbsyncer-listener/src/main/java/org/dbsyncer/listener/ChangedEvent.java → dbsyncer-sdk/src/main/java/org/dbsyncer/sdk/listener/ChangedEvent.java

@@ -1,9 +1,9 @@
 /**
- * DBSyncer Copyright 2019-2024 All Rights Reserved.
+ * DBSyncer Copyright 2019-2023 All Rights Reserved.
  */
-package org.dbsyncer.listener;
+package org.dbsyncer.sdk.listener;
 
-import org.dbsyncer.listener.model.ChangedOffset;
+import org.dbsyncer.sdk.model.ChangedOffset;
 
 import java.util.Map;
 

+ 15 - 7
dbsyncer-listener/src/main/java/org/dbsyncer/listener/Extractor.java → dbsyncer-sdk/src/main/java/org/dbsyncer/sdk/listener/Listener.java

@@ -1,9 +1,18 @@
-package org.dbsyncer.listener;
-
-import org.dbsyncer.listener.model.ChangedOffset;
-
-public interface Extractor {
-
+/**
+ * DBSyncer Copyright 2020-2023 All Rights Reserved.
+ */
+package org.dbsyncer.sdk.listener;
+
+import org.dbsyncer.sdk.model.ChangedOffset;
+
+/**
+ * 监听器接口,提供实现增量同步功能,支持定时和日志解析
+ *
+ * @Author AE86
+ * @Version 1.0.0
+ * @Date 2023-11-21 22:48
+ */
+public interface Listener {
     /**
      * 启动定时/日志抽取任务
      */
@@ -51,5 +60,4 @@ public interface Extractor {
      * @param e
      */
     void errorEvent(Exception e);
-
 }

+ 1 - 1
dbsyncer-listener/src/main/java/org/dbsyncer/listener/Watcher.java → dbsyncer-sdk/src/main/java/org/dbsyncer/sdk/listener/Watcher.java

@@ -1,4 +1,4 @@
-package org.dbsyncer.listener;
+package org.dbsyncer.sdk.listener;
 
 import java.util.Map;
 

+ 3 - 3
dbsyncer-listener/src/main/java/org/dbsyncer/listener/event/CommonChangedEvent.java → dbsyncer-sdk/src/main/java/org/dbsyncer/sdk/listener/event/CommonChangedEvent.java

@@ -1,10 +1,10 @@
 /**
  * DBSyncer Copyright 2019-2024 All Rights Reserved.
  */
-package org.dbsyncer.listener.event;
+package org.dbsyncer.sdk.listener.event;
 
-import org.dbsyncer.listener.ChangedEvent;
-import org.dbsyncer.listener.model.ChangedOffset;
+import org.dbsyncer.sdk.listener.ChangedEvent;
+import org.dbsyncer.sdk.model.ChangedOffset;
 
 import java.util.Map;
 

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio